@dataplan/pg
Version:
PostgreSQL step classes for Grafast
431 lines • 22.2 kB
TypeScript
import type { Modifier, Step } from "grafast";
import type { PgSQL, SQL, SQLRawValue } from "pg-sql2";
import { $$toSQL } from "pg-sql2";
import type { PgCodecAttributes } from "./codecs.js";
import type { PgCodecRefs, PgResource, PgResourceOptions, PgResourceParameter, PgResourceUnique } from "./datasource.js";
import type { PgExecutor } from "./executor.js";
import type { PgDeleteSingleStep } from "./steps/pgDeleteSingle.js";
import type { PgInsertSingleStep } from "./steps/pgInsertSingle.js";
import type { PgSelectQueryBuilder } from "./steps/pgSelect.js";
import type { PgSelectSingleStep } from "./steps/pgSelectSingle.js";
import type { PgUnionAllQueryBuilder } from "./steps/pgUnionAll.js";
import type { PgUpdateSingleStep } from "./steps/pgUpdateSingle.js";
/**
* A class-like source of information - could be from `SELECT`-ing a row, or
* `INSERT...RETURNING` or similar. *ALWAYS* represents a single row (or null).
*/
export type PgClassSingleStep<TResource extends PgResource<any, any, any, any, any> = PgResource> = PgSelectSingleStep<TResource> | PgInsertSingleStep<TResource> | PgUpdateSingleStep<TResource> | PgDeleteSingleStep<TResource>;
/**
* Given a value of type TInput, returns an `SQL` value to insert into an SQL
* statement.
*/
export type PgEncode<TInput> = (value: TInput) => SQLRawValue;
/**
* Given a text value from PostgreSQL, returns the value cast to TCanonical.
*/
export type PgDecode<TForJavaScript, TFromPostgres = string> = (value: TFromPostgres) => TForJavaScript;
/** @deprecated Use DataplanPg.PgRefDefinitionExtensions instead */
export type PgRefDefinitionExtensions = DataplanPg.PgRefDefinitionExtensions;
export interface PgRefDefinition {
graphqlType?: string;
sourceGraphqlType?: string;
singular?: boolean;
description?: string;
extensions?: DataplanPg.PgRefDefinitionExtensions;
singleRecordFieldName?: string;
listFieldName?: string;
connectionFieldName?: string;
}
export interface PgRefDefinitions {
[refName: string]: PgRefDefinition;
}
/** @deprecated Use DataplanPg.PgCodecExtensions instead */
export type PgCodecExtensions = DataplanPg.PgCodecExtensions;
export interface PgCodecPolymorphismSingleTypeAttributeSpec<TAttributeName extends string = string> {
attribute: TAttributeName;
isNotNull?: boolean;
rename?: string;
}
export interface PgCodecPolymorphismSingleTypeSpec<TAttributeName extends string = string> {
/** The name of the polymorphic subentry of the parent single table polymorphic codec */
name: string;
/** The attributes that are specific to this concrete type (including any modifiers); empty array is valid */
attributes: Array<PgCodecPolymorphismSingleTypeAttributeSpec<TAttributeName>>;
}
export interface PgCodecPolymorphismSingle<TAttributeName extends string = string> {
mode: "single";
/** The list of attributes that is used to determine which polymorphic type the record is. Currently this should always have length 1. */
typeAttributes: readonly TAttributeName[];
/** These attributes are shared by every concrete type of this codec; empty array is valid */
commonAttributes: readonly TAttributeName[];
/** Details the concrete types from this polymorphic single table, including what to call it, and what columns it has. */
types: {
[typeKey: string]: PgCodecPolymorphismSingleTypeSpec<TAttributeName>;
};
}
export interface PgCodecPolymorphismRelationalTypeSpec {
name: string;
/** The name of the database table this type relates to (useful before the relations are established) */
references: string;
/** The name of the relation to follow to get the related record */
relationName: string;
}
export interface PgCodecPolymorphismRelational<TAttributeName extends string = string> {
mode: "relational";
typeAttributes: readonly TAttributeName[];
types: {
[typeKey: string]: PgCodecPolymorphismRelationalTypeSpec;
};
}
export interface PgCodecPolymorphismUnion {
mode: "union";
}
export type PgCodecPolymorphism<TAttributeName extends string> = PgCodecPolymorphismSingle<TAttributeName> | PgCodecPolymorphismRelational<TAttributeName> | PgCodecPolymorphismUnion;
/**
* A codec for a Postgres type, tells us how to convert to-and-from Postgres
* (including changes to the SQL statement itself). Also includes metadata
* about the type, for example any of the attributes it has.
*/
export interface PgCodec<TName extends string = string, TAttributes extends PgCodecAttributes | undefined = PgCodecAttributes | undefined, TFromPostgres = any, TFromJavaScript = TFromPostgres, TArrayItemCodec extends PgCodec<string, any, any, any, any, any, any> | undefined = PgCodec<string, any, any, any, any, any, any> | undefined, TDomainItemCodec extends PgCodec<string, any, any, any, any, any, any> | undefined = PgCodec<string, any, any, any, any, any, any> | undefined, TRangeItemCodec extends PgCodec<string, undefined, any, any, undefined, any, undefined> | undefined = PgCodec<string, undefined, any, any, undefined, any, undefined> | undefined> {
/**
* Unique name to identify this codec.
*/
name: TName;
/**
* 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;
/**
* 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 true, this is an anonymous type (e.g. the return type of a
* `returns record` or `returns table` PostgreSQL function) and thus should
* not be referenced via `sqlType` directly.
*/
isAnonymous?: boolean;
/**
* True if this type is a binary type (e.g. bytea)
*/
isBinary?: boolean;
/**
* True if this type is an enum type
*/
isEnum?: 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;
/**
* If this is a composite type, the attributes it supports.
*/
attributes: TAttributes;
/**
* A callback to return `'true'` (text string) if the composite type
* represented by this codec is non-null, and `null` or `'false'` otherwise.
*
* If this codec represents a composite type (e.g. a row or other type with
* multiple attributes) and this type can be returned from a function then
* there's a risk that the function may return null/an all-nulls composite
* type. This can occur with `returns some_composite_type` or
* `returns setof some_composite_type`, though the former is more common as
* you explicitly need to return nulls in the latter.
*
* We can't simply do `not (foo is null)` because you might be using
* column-level select grants which would prevent this happening. As such we
* give you a chance to provide your own non-null check. In most table cases
* you can use `(${alias}.id is not null)::text` (assuming 'id' is the name
* of your primary key); for composite types you can normally do
* `(not (${alias} is null))::text`.
*/
notNullExpression?: (alias: SQL) => SQL;
/**
* If set, this represents a PostgreSQL array type. Please note: array types
* should NOT be nested.
*/
arrayOfCodec?: TArrayItemCodec;
/**
* The underlying codec that this type is a domain over.
*/
domainOfCodec?: TDomainItemCodec;
/**
* If this is a domain, does it add a non-null constraint?
*/
notNull?: boolean;
/**
* The underlying codec that this type is a range over.
*/
rangeOfCodec?: TRangeItemCodec;
polymorphism?: PgCodecPolymorphism<any>;
description?: string;
/**
* Arbitrary metadata
*/
extensions?: Partial<PgCodecExtensions>;
/**
* Relations to follow for shortcut references, can be polymorphic, can be
* many-to-many.
*/
refs?: PgCodecRefs;
/**
* If this codec came from a specific database, we should list the executor
* here. If the codec is used with multiple databases, leave this null, but
* note that if it has attributes then it will not be able to be used as the
* type of an attribute itself.
*/
executor: PgExecutor | null;
}
export type PgCodecWithAttributes<TAttributes extends PgCodecAttributes = PgCodecAttributes> = PgCodec<any, TAttributes, any, any, undefined, any, undefined>;
export type PgCodecAnyScalar = PgCodec<string, undefined, any, any, undefined, any, any>;
export type PgCodecList<TInnerCodec extends PgCodec<string, any, any, any, any, any, any> = PgCodec<string, any, any, any, any, any, any>> = PgCodec<string, undefined, any, any, TInnerCodec, undefined, undefined>;
export type PgEnumValue<TValue extends string = string> = {
value: TValue;
description?: string;
};
/**
* A PgCodec specifically for enums
*/
export interface PgEnumCodec<TName extends string = string, TValue extends string = string> extends PgCodec<TName, undefined, string, TValue, undefined, undefined, undefined> {
values: PgEnumValue<TValue>[];
isEnum: true;
}
/**
* A PgTypedStep has a 'pgCodec' property which means we don't need
* to also state the pgCodec to use, this can be an added convenience.
*/
export interface PgTypedStep<TCodec extends PgCodec> extends Step {
pgCodec: TCodec;
}
type PgOrderCommonSpec = {
readonly direction: "ASC" | "DESC";
/** `NULLS FIRST` or `NULLS LAST` or nothing */
readonly nulls?: "FIRST" | "LAST" | null;
};
export type PgOrderFragmentSpec = {
/** The expression we're ordering by. */
readonly fragment: SQL;
/** The codec of the expression that we're ordering by, this is useful when constructing a cursor for it. */
readonly codec: PgCodec<string, any, any, any, any, any, any>;
readonly attribute?: never;
readonly callback?: never;
readonly nullable?: boolean;
} & PgOrderCommonSpec;
export type PgOrderAttributeSpec = {
/** The attribute you're using for ordering */
readonly attribute: string;
/** An optional expression to wrap this attribute with, and the type that expression returns */
readonly callback?: (attributeExpression: SQL, attributeCodec: PgCodec, nullable: boolean) => [fragment: SQL, codec: PgCodec, nullable?: boolean];
readonly fragment?: never;
readonly codec?: never;
readonly nullable?: boolean;
} & PgOrderCommonSpec;
/**
* The information required to specify an entry in an 'ORDER BY' clause.
*/
export type PgOrderSpec = PgOrderFragmentSpec | PgOrderAttributeSpec;
/**
* The information required to specify an entry in a `GROUP BY` clause.
*/
export interface PgGroupSpec {
fragment: SQL;
codec: PgCodec<string, any, any, any>;
guaranteedNotNull?: boolean;
}
export type TuplePlanMap<TAttributes extends PgCodecAttributes, TTuple extends ReadonlyArray<keyof TAttributes>> = {
[Index in keyof TTuple]: {
[key in keyof TAttributes as Exclude<key, keyof TTuple[number]>]?: Step<ReturnType<TAttributes[key]["codec"]["fromPg"]>>;
} & {
[key in TTuple[number]]: Step<ReturnType<TAttributes[key]["codec"]["fromPg"]>>;
};
};
/**
* Represents a spec like `{user_id: Step}` or
* `{organization_id: Step, item_id: Step}`. The keys in
* the spec can be any of the attributes in TAttributes, however there must be at
* least one of the unique sets of attributes represented (as specified in
* TUniqueAttributes) - you can then add arbitrary additional attributes if you need
* to.
*/
export type PlanByUniques<TAttributes extends PgCodecAttributes, TUniqueAttributes extends ReadonlyArray<PgResourceUnique<TAttributes>>> = TAttributes extends PgCodecAttributes ? TuplePlanMap<TAttributes, TUniqueAttributes[number]["attributes"] & string[]>[number] : undefined;
export type PgConditionLike = Modifier<any> & {
alias: SQL;
where(condition: SQL): void;
having(condition: SQL): void;
};
export type KeysOfType<TObject, TValueType> = {
[key in keyof TObject]: TObject[key] extends TValueType ? key : never;
}[keyof TObject];
export interface MakePgServiceOptions extends Partial<Pick<GraphileConfig.PgServiceConfiguration, "name" | "pgSettings" | "pgSettingsKey" | "pgSettingsForIntrospection" | "withPgClientKey" | "pgSubscriber" | "pgSubscriberKey">> {
connectionString?: string;
schemas?: string | string[];
superuserConnectionString?: string;
pubsub?: boolean;
}
/** @deprecated Use DataplanPg.PgCodecRelationExtensions instead */
export type PgCodecRelationExtensions = DataplanPg.PgCodecRelationExtensions;
export interface PgCodecRelationBase<TLocalCodec extends PgCodec = PgCodec, TRemoteAttributes extends string = string> {
/** Where the relationship starts */
localCodec: TLocalCodec;
/** If localCodec is polymorphic, which of the concrete subtypes should this relationship apply to? */
localCodecPolymorphicTypes?: string[];
/**
* The attributes locally used in this relationship.
*/
localAttributes: readonly (keyof TLocalCodec["attributes"] & string)[];
/**
* The remote attributes that are joined against.
*/
remoteAttributes: readonly TRemoteAttributes[];
/**
* If true then there's at most one record this relationship will find.
*/
isUnique: boolean;
/**
* If true then this is a reverse lookup (where our local attributes are
* referenced by the remote tables remote attributes, rather than the other way
* around), so multiple rows may be found (unless isUnique is true).
*/
isReferencee?: boolean;
/**
* Space for you to add your own metadata.
*/
extensions?: PgCodecRelationExtensions;
description?: string;
}
export interface PgCodecRelationConfig<TLocalCodec extends PgCodec = PgCodecWithAttributes, TRemoteResourceOptions extends PgResourceOptions = PgResourceOptions<any, PgCodecWithAttributes, any, any>> extends PgCodecRelationBase<TLocalCodec, TRemoteResourceOptions extends PgResourceOptions<any, PgCodec<any, infer UAttributes, any, any, any, any, any>, any, any> ? keyof UAttributes : never> {
remoteResourceOptions: TRemoteResourceOptions;
}
/**
* Describes a relation from a codec to a resource
*/
export interface PgCodecRelation<TLocalCodec extends PgCodecWithAttributes = PgCodecWithAttributes, TRemoteResource extends PgResource<any, PgCodecWithAttributes, any, any, any> = PgResource<any, PgCodecWithAttributes, any, any, any>> extends PgCodecRelationBase<TLocalCodec, TRemoteResource extends PgResource<any, PgCodec<any, infer UAttributes, any, any, any, any, any>, any, any, any> ? keyof UAttributes : never> {
/**
* The remote resource this relation relates to.
*/
remoteResource: TRemoteResource;
}
export interface PgRegistryConfig<TCodecs extends {
[name in string]: PgCodec<name, PgCodecAttributes | undefined, any, any, any, any, any>;
}, TResourceOptions extends {
[name in string]: PgResourceOptions<name, PgCodec, ReadonlyArray<PgResourceUnique<PgCodecAttributes<any>>>, readonly PgResourceParameter[] | undefined>;
}, TRelations extends {
[codecName in keyof TCodecs]?: {
[relationName in string]: PgCodecRelationConfig<PgCodec<string, PgCodecAttributes, any, any, undefined, any, undefined>, PgResourceOptions<any, PgCodecWithAttributes, any, any>>;
};
}, TExecutors extends {
[executorName in string]: PgExecutor<any>;
} = {
[executorName: string]: PgExecutor<any>;
}> {
pgExecutors: TExecutors;
pgCodecs: TCodecs;
pgResources: TResourceOptions;
pgRelations: TRelations;
}
export type Expand<T> = T extends unknown ? {
[TKey in keyof T]: T[TKey];
} : never;
export interface PgRegistry<TCodecs extends {
[name in string]: PgCodec<name, PgCodecAttributes | undefined, any, any, any, any, any>;
} = Record<string, PgCodec<string, PgCodecAttributes | undefined, any, any, any, any, any>>, TResourceOptions extends {
[name in string]: PgResourceOptions<name, PgCodec, // TCodecs[keyof TCodecs],
ReadonlyArray<PgResourceUnique<PgCodecAttributes>>, readonly PgResourceParameter[] | undefined>;
} = Record<string, PgResourceOptions<string, PgCodecWithAttributes, // TCodecs[keyof TCodecs],
ReadonlyArray<PgResourceUnique<PgCodecAttributes>>, readonly PgResourceParameter[] | undefined>>, TRelations extends {
[codecName in keyof TCodecs]?: {
[relationName in string]: PgCodecRelationConfig<PgCodec<string, PgCodecAttributes, any, any, undefined, any, undefined>, PgResourceOptions<any, PgCodecWithAttributes, any, any>>;
};
} = Record<string, Record<string, PgCodecRelationConfig<PgCodec<string, PgCodecAttributes, any, any, undefined, any, undefined>, PgResourceOptions<any, PgCodecWithAttributes, any, any>>>>, TExecutors extends {
[executorName in string]: PgExecutor<any>;
} = {
[executorName: string]: PgExecutor<any>;
}> {
pgExecutors: TExecutors;
pgCodecs: TCodecs;
pgResources: {
[name in keyof TResourceOptions]: TResourceOptions[name] extends PgResourceOptions<infer UName, infer UCodec, infer UUniques, infer UParameters> ? PgResource<UName, UCodec, UUniques, UParameters, PgRegistry<TCodecs, TResourceOptions, TRelations>> : never;
};
pgRelations: {
[codecName in keyof TRelations]: {
[relationName in keyof TRelations[codecName]]: Expand<Omit<TRelations[codecName][relationName], "remoteResourceOptions"> & {
remoteResource: TRelations[codecName][relationName] extends {
remoteResourceOptions: PgResourceOptions<infer UName, infer UCodec, infer UUniques, infer UParameters>;
} ? PgResource<UName, UCodec, UUniques, UParameters, PgRegistry<TCodecs, TResourceOptions, TRelations>> : never;
}>;
};
};
}
export type GetPgRegistryCodecs<TRegistry extends PgRegistry<any, any, any, any>> = TRegistry["pgCodecs"];
export type GetPgRegistrySources<TRegistry extends PgRegistry<any, any, any, any>> = TRegistry["pgResources"];
export type GetPgRegistryCodecRelations<TRegistry extends PgRegistry<any, any, any, any>, TCodec extends PgCodec<any, any, any, any, any, any, any>> = TRegistry["pgRelations"][TCodec["name"]];
export type GetPgCodecAttributes<TCodec extends PgCodec<any, any, any, any, any, any, any>> = TCodec extends PgCodec<any, infer UAttributes, any, any, any, any, any> ? UAttributes extends undefined ? never : UAttributes : PgCodecAttributes;
export type GetPgResourceRegistry<TResource extends PgResource<any, any, any, any, any>> = TResource["registry"];
export type GetPgResourceCodec<TResource extends PgResource<any, any, any, any, any>> = TResource["codec"];
export type GetPgResourceAttributes<TResource extends PgResource<any, PgCodecWithAttributes, any, any, any>> = GetPgCodecAttributes<TResource["codec"]>;
export type GetPgResourceRelations<TResource extends PgResource<any, any, any, any, any>> = TResource["registry"]["pgRelations"][TResource["codec"]["name"]];
export type GetPgResourceUniques<TResource extends PgResource<any, any, any, any, any>> = TResource["uniques"];
export type PgSQLCallback<TResult> = (sql: PgSQL<PgTypedStep<PgCodec>>) => TResult;
export type PgSQLCallbackOrDirect<TResult> = PgSQLCallback<TResult> | TResult;
export interface PgQueryBuilder {
/** The alias of the current table */
alias: SQL;
[$$toSQL](): SQL;
setMeta(key: string, value: unknown): void;
getMetaRaw(key: string): unknown;
}
export type PgSelectQueryBuilderCallback = (qb: PgSelectQueryBuilder) => void;
export type PgUnionAllQueryBuilderCallback = (qb: PgUnionAllQueryBuilder) => void;
export type ReadonlyArrayOrDirect<T> = T | ReadonlyArray<T>;
export type ObjectForResource<TResource extends PgResource<any, PgCodecWithAttributes, any, any, any>> = {
[key in keyof GetPgResourceAttributes<TResource> & string]?: any;
};
export interface PgQueryRootStep extends Step {
getPgRoot(): PgQueryRootStep;
placeholder($step: PgTypedStep<PgCodec>): SQL;
placeholder($step: Step, codec: PgCodec, alreadyEncoded?: boolean): SQL;
deferredSQL($step: Step<SQL>): SQL;
}
export {};
//# sourceMappingURL=interfaces.d.ts.map