diff --git a/.direnv/flake-inputs/v3my1jzicdrbzp8z1kij37h906a0zzbg-source b/.direnv/flake-inputs/v3my1jzicdrbzp8z1kij37h906a0zzbg-source deleted file mode 120000 index c758e31e29..0000000000 --- a/.direnv/flake-inputs/v3my1jzicdrbzp8z1kij37h906a0zzbg-source +++ /dev/null @@ -1 +0,0 @@ -/nix/store/v3my1jzicdrbzp8z1kij37h906a0zzbg-source \ No newline at end of file diff --git a/.gitignore b/.gitignore index aae85fdc63..c9ffee653d 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,5 @@ grafast/dataplan-pg/__tests__/**/*.mermaid.png /WHAT_ARE_YOU_DOING.md contrib /env +.direnv +.vscode \ No newline at end of file diff --git a/grafast/dataplan-pg/src/codecs.ts b/grafast/dataplan-pg/src/codecs.ts index cc35724d9e..3bcdce9c40 100644 --- a/grafast/dataplan-pg/src/codecs.ts +++ b/grafast/dataplan-pg/src/codecs.ts @@ -38,8 +38,14 @@ import { import type { PgExecutor } from "./executor.js"; import { inspect } from "./inspect.js"; import type { + AnyPgCodec, + DefaultPgCodec, PgCodec, + PgCodecAttributeMap, + PgCodecAttributes, + PgCodecAttributesRecord, PgCodecExtensions, + PgCodecFromPostgres, PgCodecPolymorphism, PgDecode, PgEncode, @@ -50,19 +56,59 @@ import type { // PERF: `identity` can be shortcut const identity = (value: T): T => value; -export type PgCodecAttributeViaExplicit = { - relation: string; - attribute: string; +export type PgCodecAttributeViaExplicit< + TRelationName extends string, + TAttribute extends string, +> = { + relation: TRelationName; + attribute: TAttribute; }; -export type PgCodecAttributeVia = string | PgCodecAttributeViaExplicit; +export interface AnyPgCodecAttributeVia extends PgCodecAttributeVia {} +export type DefaultPgCodecAttributeVia = PgCodecAttributeVia; +export type AnyPgCodecAttributeViaRelationName = + U extends PgCodecAttributeVia + ? TRelationName + : never; +export type AnyPgCodecAttributeViaAttribute = U extends PgCodecAttributeVia< + any, + infer TAttributeName +> + ? TAttributeName + : never; +export type PgCodecAttributeVia< + TRelationName extends string, + TAttribute extends string, +> = TRelationName | PgCodecAttributeViaExplicit; /** @deprecated Use DataplanPg.PgCodecAttributeExtensions instead */ export type PgCodecAttributeExtensions = DataplanPg.PgCodecAttributeExtensions; +export type PgCodecAttributeName = U extends PgCodecAttribute< + infer TName, + any, + any +> + ? TName + : never; + +export type PgCodecAttributeCodec = U extends PgCodecAttribute< + any, + infer TCodec, + any +> + ? TCodec + : never; + +export interface DefaultPgCodecAttribute + extends PgCodecAttribute {} +export interface AnyPgCodecAttribute extends PgCodecAttribute {} + export interface PgCodecAttribute< - TCodec extends PgCodec = PgCodec, - TNotNull extends boolean = boolean, + TName extends string, + TCodec extends AnyPgCodec, + TNotNull extends boolean, > { + name: TName; /** * How to translate to/from PG and how to cast. */ @@ -87,7 +133,7 @@ export interface PgCodecAttribute< * If this attribute actually exists on a relation rather than locally, the name * of the (unique) relation this attribute belongs to. */ - via?: PgCodecAttributeVia; + via?: AnyPgCodecAttributeVia; /** * If the attribute exists identically on a relation and locally (e.g. @@ -112,7 +158,7 @@ export interface PgCodecAttribute< * these are all plural relationships. So identicalVia is generally one-way * (except in 1-to-1 relationships). */ - identicalVia?: PgCodecAttributeVia; + identicalVia?: AnyPgCodecAttributeVia; // ENHANCE: can identicalVia be plural? Is that useful? Maybe a attribute that has // multiple foreign key references? @@ -129,14 +175,6 @@ export interface PgCodecAttribute< extensions?: Partial; } -export type PgCodecAttributes< - TCodecMap extends { - [attributeName in string]: PgCodecAttribute; - } = { - [attributeName in string]: PgCodecAttribute; - }, -> = TCodecMap; - /** * Returns a PgCodec for the given builtin Postgres scalar type, optionally * pass the following config: @@ -160,12 +198,12 @@ function t(): < options?: Cast, ) => PgCodec< TName, - undefined, + never, TFromPostgres, TFromJavaScript, - undefined, - undefined, - undefined + never, + never, + never > { return (oid, type, options = {}) => { const { castFromPg, listCastFromPg, fromPg, toPg, isBinary } = options; @@ -174,7 +212,6 @@ function t(): < sqlType: sql.identifier(...type.split(".")), fromPg: fromPg ?? (identity as any), toPg: toPg ?? (identity as any), - attributes: undefined, extensions: { oid: oid }, castFromPg, listCastFromPg, @@ -318,11 +355,21 @@ function recordStringToTuple(value: string): Array { return tuple; } -function realAttributeDefs( +function realAttributeDefs< + TAttributes extends Record, +>( attributes: TAttributes, -): Array<[string, TAttributes[keyof TAttributes]]> { +): Array< + [ + PgCodecAttributeName, + TAttributes[keyof TAttributes], + ] +> { const attributeDefs = Object.entries(attributes) as Array< - [string, TAttributes extends infer U ? U[keyof U] : never] + [ + PgCodecAttributeName, + TAttributes[keyof TAttributes], + ] >; return attributeDefs.filter( ([_attributeName, spec]) => !spec.expression && !spec.via, @@ -336,9 +383,9 @@ function realAttributeDefs( * * @see {@link https://www.postgresql.org/docs/current/rowtypes.html#id-1.5.7.24.6} */ -function makeRecordToSQLRawValue( - attributes: TAttributes, -): PgEncode> { +function makeRecordToSQLRawValue< + TAttributes extends Record, +>(attributes: TAttributes): PgEncode> { const attributeDefs = realAttributeDefs(attributes); return (value) => { const values = attributeDefs.map(([attributeName, spec]) => { @@ -350,19 +397,23 @@ function makeRecordToSQLRawValue( }; } -export type ObjectFromPgCodecAttributes = - { - [attributeName in keyof TAttributes]: TAttributes[attributeName] extends PgCodecAttribute< - infer UCodec, - infer UNonNull - > - ? UCodec extends PgCodec - ? UNonNull extends true - ? Exclude - : UFromJs | null - : never - : never; - }; +export type ObjectFromPgCodecAttributes< + TAttributes extends Record, +> = { + [TCodecAttribute in keyof TAttributes as PgCodecAttributeName< + TAttributes[TCodecAttribute] + >]: TAttributes[TCodecAttribute] extends PgCodecAttribute< + any, + infer UCodec, + infer UNonNull + > + ? UCodec extends PgCodec + ? UNonNull extends true + ? Exclude + : UFromJs | null + : never + : never; +}; /** * Takes a list of attributes and returns a mapping function that takes a @@ -371,7 +422,9 @@ export type ObjectFromPgCodecAttributes = * * @see {@link https://www.postgresql.org/docs/current/rowtypes.html#id-1.5.7.24.6} */ -function makeSQLValueToRecord( +function makeSQLValueToRecord< + TAttributes extends Record, +>( attributes: TAttributes, ): (value: string) => ObjectFromPgCodecAttributes { const attributeDefs = realAttributeDefs(attributes); @@ -390,7 +443,9 @@ function makeSQLValueToRecord( export type PgRecordTypeCodecSpec< TName extends string, - TAttributes extends PgCodecAttributes, + TAttributes extends { + [TAttribute in keyof TAttributes]: Omit; + }, > = { name: TName; executor: PgExecutor; @@ -414,17 +469,28 @@ export type PgRecordTypeCodecSpec< */ export function recordCodec< const TName extends string, - const TAttributes extends PgCodecAttributes, + const TAttributes extends { + [TAttribute in keyof TAttributes]: Omit; + }, + const TCodecAttributes extends { + [TAttribute in keyof TAttributes]: PgCodecAttribute< + TAttribute extends string ? TAttribute : never, + TAttributes[TAttribute]["codec"], + TAttributes[TAttribute]["notNull"] extends boolean + ? TAttributes[TAttribute]["notNull"] + : never + >; + }, >( config: PgRecordTypeCodecSpec, ): PgCodec< TName, - TAttributes, + TCodecAttributes, string, - ObjectFromPgCodecAttributes, - undefined, - undefined, - undefined + ObjectFromPgCodecAttributes, + never, + never, + never > { const { name, @@ -436,13 +502,26 @@ export function recordCodec< isAnonymous = false, executor, } = config; + + ( + Object.entries(attributes) as Array< + [keyof TAttributes, TAttributes[keyof TAttributes]] + > + ).forEach( + ([name, value]) => + (attributes[name] = { + ...value, + name, + }), + ); + return { name, sqlType: identifier, isAnonymous, - fromPg: makeSQLValueToRecord(attributes), - toPg: makeRecordToSQLRawValue(attributes), - attributes, + fromPg: makeSQLValueToRecord(attributes as unknown as TCodecAttributes), + toPg: makeRecordToSQLRawValue(attributes as unknown as TCodecAttributes), + attributes: attributes as unknown as TCodecAttributes, polymorphism, description, extensions, @@ -506,14 +585,14 @@ type CodecWithListCodec< `${TCodec extends PgCodec ? UName : never}[]`, - undefined, + never, string, - TCodec extends PgCodec + TCodec extends PgCodec ? UFromJs[] : any[], TCodec, - undefined, - undefined + never, + never >; }; @@ -530,9 +609,7 @@ type CodecWithListCodec< * @param typeDelim - the delimeter used to separate entries in this list when Postgres stringifies it * @param identifier - a pg-sql2 fragment that represents the name of this type */ -export function listOfCodec< - TInnerCodec extends PgCodec, ->( +export function listOfCodec( listedCodec: TInnerCodec, config?: { description?: string; @@ -544,14 +621,14 @@ export function listOfCodec< `${TInnerCodec extends PgCodec ? UName : never}[]`, - undefined, // Array has no attributes + never, // Array has no attributes string, - TInnerCodec extends PgCodec + TInnerCodec extends PgCodec ? UFromJs[] : any[], TInnerCodec, - undefined, - undefined + never, + never > { const innerCodec: CodecWithListCodec = listedCodec; @@ -570,22 +647,14 @@ export function listOfCodec< `${TInnerCodec extends PgCodec ? UName : never}[]`, - undefined, // Array has no attributes + never, // Array has no attributes string, - TInnerCodec extends PgCodec< - any, - any, - any, - infer UFromJs, - undefined, - any, - any - > + TInnerCodec extends PgCodec ? UFromJs[] : any[], TInnerCodec, - undefined, - undefined + never, + never > = { name: `${ innerCodec.name as TInnerCodec extends PgCodec< @@ -663,7 +732,7 @@ exportAs("@dataplan/pg", listOfCodec, "listOfCodec"); */ export function domainOfCodec< TName extends string, - TInnerCodec extends PgCodec, + TInnerCodec extends AnyPgCodec, >( innerCodec: TInnerCodec, name: TName, @@ -675,13 +744,15 @@ export function domainOfCodec< } = {}, ): PgCodec< TName, - TInnerCodec extends PgCodec ? U : any, - TInnerCodec extends PgCodec ? U : any, - undefined, + PgCodecAttributeMap, + PgCodecFromPostgres, + any, + AnyPgCodec, TInnerCodec, - undefined + any > { const { description, extensions, notNull } = config; + return { // Generally same as underlying type: ...innerCodec, @@ -691,7 +762,7 @@ export function domainOfCodec< sqlType: identifier, description, extensions, - domainOfCodec: innerCodec.arrayOfCodec ? undefined : innerCodec, + ...(innerCodec.arrayOfCodec ? {} : { domainOfCodec: innerCodec }), notNull: Boolean(notNull), }; } @@ -702,9 +773,10 @@ exportAs("@dataplan/pg", domainOfCodec, "domainOfCodec"); * * @internal */ -function escapeRangeValue< - TInnerCodec extends PgCodec, ->(value: null | any, innerCodec: TInnerCodec): string { +function escapeRangeValue( + value: null | any, + innerCodec: TInnerCodec, +): string { if (value == null) { return ""; } @@ -729,15 +801,7 @@ interface PgRange { */ export function rangeOfCodec< TName extends string, - TInnerCodec extends PgCodec< - any, - undefined, - any, - any, - undefined, - any, - undefined - >, + TInnerCodec extends AnyPgCodec, >( innerCodec: TInnerCodec, name: TName, @@ -746,15 +810,7 @@ export function rangeOfCodec< description?: string; extensions?: Partial; } = {}, -): PgCodec< - TName, - undefined, - string, - PgRange, - undefined, - undefined, - TInnerCodec -> { +): PgCodec, never, never, TInnerCodec> { const { description, extensions } = config; const needsCast = innerCodec.castFromPg; @@ -1171,9 +1227,7 @@ export function getCodecByPgCatalogTypeName(pgCatalogTypeName: string) { return null; } -export function getInnerCodec< - TCodec extends PgCodec, ->( +export function getInnerCodec( codec: TCodec, ): TCodec extends PgCodec< any, @@ -1181,7 +1235,8 @@ export function getInnerCodec< any, infer UArray, infer UDomain, - infer URange + infer URange, + any > ? Exclude : TCodec { diff --git a/grafast/dataplan-pg/src/datasource.ts b/grafast/dataplan-pg/src/datasource.ts index c61b7034c2..3d772caf2f 100644 --- a/grafast/dataplan-pg/src/datasource.ts +++ b/grafast/dataplan-pg/src/datasource.ts @@ -1,5 +1,3 @@ -/* eslint-disable graphile-export/export-instances */ -import chalk from "chalk"; import type { GrafastValuesList, ObjectStep } from "grafast"; import { __ValueStep, @@ -13,7 +11,9 @@ import type { SQL } from "pg-sql2"; import sql from "pg-sql2"; import type { - PgCodecAttributes, + AnyPgCodecAttribute, + DefaultPgCodecAttribute, + PgCodecAttributeName, PgCodecAttributeVia, PgCodecAttributeViaExplicit, } from "./codecs.js"; @@ -28,18 +28,29 @@ import type { } from "./executor.js"; import { inspect } from "./inspect.js"; import type { - Expand, - GetPgCodecAttributes, + AnyPgCodecRelationConfig, + PgCodecAttributes, GetPgRegistryCodecRelations, - GetPgRegistryCodecs, PgCodec, - PgCodecRelation, + PgCodecName, PgCodecRelationConfig, - PgCodecWithAttributes, PgRefDefinition, PgRegistry, PgRegistryConfig, PlanByUniques, + AnyPgCodec, + AnyPgRegistry, + GetPgRegistryCodecRelationConfigs, + GetPgRegistryCodecs, + AnyPgRegistryConfig, + AnyScalarPgCodec, + PgCodecRelationConfigName, + PgRegistryConfigCodecs, + PgRegistryConfigRelationConfigs, + PgRegistryConfigResourceOptions, + Expand, + DefaultPgCodec, + DefaultPgCodecRelationConfig, } from "./interfaces.js"; import type { PgClassExpressionStep } from "./steps/pgClassExpression.js"; import type { @@ -54,6 +65,7 @@ import type { PgSelectSinglePlanOptions, PgSelectSingleStep, } from "./steps/pgSelectSingle.js"; +import chalk from "chalk"; export function EXPORTABLE( factory: (...args: TScope) => T, @@ -82,13 +94,17 @@ export type PgResourceExtensions = DataplanPg.PgResourceExtensions; export type PgResourceParameterExtensions = DataplanPg.PgResourceParameterExtensions; +export interface AnyPgResourceParameter extends PgResourceParameter {} +export interface DefaultPgResourceParameter + extends PgResourceParameter {} + /** * If this is a functional (rather than static) resource, this describes one of * the parameters it accepts. */ export interface PgResourceParameter< - TName extends string | null = string | null, - TCodec extends PgCodec = PgCodec, + TName extends string | null, + TCodec extends AnyPgCodec, > { /** * Name of the parameter, if null then we must use positional rather than @@ -111,16 +127,17 @@ export interface PgResourceParameter< extensions?: PgResourceParameterExtensions; } +export interface AnyPgResourceUnique extends PgResourceUnique {} +export interface DefaultPgResourceUnique + extends PgResourceUnique {} /** * Description of a unique constraint on a PgResource. */ -export interface PgResourceUnique< - TAttributes extends PgCodecAttributes = PgCodecAttributes, -> { +export interface PgResourceUnique { /** * The attributes that are unique */ - attributes: ReadonlyArray; + attributes: Array>; /** * If this is true, this represents the "primary key" of the resource. */ @@ -151,19 +168,59 @@ export interface PgCodecRef { export interface PgCodecRefs { [refName: string]: PgCodecRef; } +export interface DefaultPgResourceOptions< + TCodec extends DefaultPgCodec = DefaultPgCodec, +> extends PgResourceOptions< + string, + TCodec, + DefaultPgResourceUnique, + DefaultPgResourceParameter + > {} + +export interface AnyPgResourceOptions + extends PgResourceOptions {} + +export type PgResourceOptionName = U extends PgResourceOptions< + infer TName, + any, + any, + any +> + ? TName + : never; +export type PgResourceOptionCodec = U extends PgResourceOptions< + any, + infer TCodec, + any, + any +> + ? TCodec + : never; +export type PgResourceOptionUniques = U extends PgResourceOptions< + any, + any, + infer TUniques, + any +> + ? TUniques + : never; +export type PgResourceOptionParameters = U extends PgResourceOptions< + any, + any, + any, + infer TParameters +> + ? TParameters + : never; /** * Configuration options for your PgResource */ export interface PgResourceOptions< - TName extends string = string, - TCodec extends PgCodec = PgCodec, - TUniques extends ReadonlyArray< - PgResourceUnique> - > = ReadonlyArray>>, - TParameters extends readonly PgResourceParameter[] | undefined = - | readonly PgResourceParameter[] - | undefined, + TName extends string, + TCodec extends AnyPgCodec, + TUniques extends PgResourceUnique>, + TParameters extends AnyPgResourceParameter, > { /** * The associated codec for this resource @@ -177,18 +234,17 @@ export interface PgResourceOptions< executor: PgExecutor; // TODO: auth should also apply to insert, update and delete, maybe via insertAuth, updateAuth, etc - selectAuth?: ( - $step: PgSelectStep>, - ) => void; + selectAuth?: ($step: PgSelectStep) => void; name: TName; identifier?: string; - from: TParameters extends readonly PgResourceParameter[] - ? (...args: PgSelectArgumentDigest[]) => SQL - : SQL; - uniques?: TUniques; + // see https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types + from: SQL | ((...args: PgSelectArgumentDigest[]) => SQL); + uniques?: [TUniques] extends [never] ? never : ReadonlyArray; extensions?: PgResourceExtensions; - parameters?: TParameters; + parameters?: [TParameters] extends [never] + ? never + : ReadonlyArray; description?: string; /** * Set true if this resource will only return at most one record - this is @@ -213,44 +269,104 @@ export interface PgResourceOptions< isVirtual?: boolean; } +export interface AnyPgFunctionResourceOptions + extends PgFunctionResourceOptions {} +export interface DefaultPgFunctionResourceOptions + extends PgFunctionResourceOptions< + string, + DefaultPgCodec, + DefaultPgResourceUnique, + DefaultPgResourceParameter + > {} + export interface PgFunctionResourceOptions< - TNewName extends string = string, - TCodec extends PgCodec = PgCodec, - TUniques extends ReadonlyArray< - PgResourceUnique> - > = ReadonlyArray>>, - TNewParameters extends - readonly PgResourceParameter[] = readonly PgResourceParameter[], + TNewName extends string, + TCodec extends AnyPgCodec, + TUniques extends PgResourceUnique>, + TNewParameters extends AnyPgResourceParameter, > { name: TNewName; identifier?: string; from: (...args: PgSelectArgumentDigest[]) => SQL; - parameters: TNewParameters; + parameters?: [TNewParameters] extends [never] + ? never + : ReadonlyArray; returnsSetof: boolean; returnsArray: boolean; - uniques?: TUniques; + uniques?: [TUniques] extends [never] ? never : ReadonlyArray; extensions?: PgResourceExtensions; isMutation?: boolean; - selectAuth?: ( - $step: PgSelectStep>, - ) => void; + selectAuth?: ($step: PgSelectStep) => void; description?: string; } +export type PgResourceName = U extends PgResource< + infer TName, + any, + any, + any, + any +> + ? TName + : never; +export type PgResourceCodec = U extends PgResource< + any, + infer TCodec, + any, + any, + any +> + ? TCodec + : never; + +export type PgResourceUniques = U extends PgResource< + any, + any, + infer TUniques, + any, + any +> + ? TUniques + : never; +export type PgResourceParameters = U extends PgResource< + any, + any, + any, + infer TParameters, + any +> + ? TParameters + : never; +export type PgResourceRegistry = U extends PgResource< + any, + any, + any, + any, + infer TRegistry +> + ? TRegistry + : never; +export interface DefaultPgResource + extends PgResource< + string, + DefaultPgCodec, + DefaultPgResourceUnique, + DefaultPgResourceParameter, + any + > {} +export interface AnyPgResource extends PgResource {} +export interface AnyScalarPgResource + extends PgResource {} /** * PgResource represents any resource of SELECT-able data in Postgres: tables, * views, functions, etc. */ export class PgResource< - TName extends string = string, - TCodec extends PgCodec = PgCodec, - TUniques extends ReadonlyArray< - PgResourceUnique> - > = ReadonlyArray>>, - TParameters extends readonly PgResourceParameter[] | undefined = - | readonly PgResourceParameter[] - | undefined, - TRegistry extends PgRegistry = PgRegistry, + TName extends string, + TCodec extends AnyPgCodec, + TUniques extends PgResourceUnique>, + TParameters extends AnyPgResourceParameter, + TRegistry extends AnyPgRegistry, > { public readonly registry: TRegistry; public readonly codec: TCodec; @@ -258,10 +374,10 @@ export class PgResource< public readonly name: TName; public readonly identifier: string; public readonly from: SQL | ((...args: PgSelectArgumentDigest[]) => SQL); - public readonly uniques: TUniques; - private selectAuth?: ( - $step: PgSelectStep>, - ) => void; + public readonly uniques: [TUniques] extends [never] + ? never + : ReadonlyArray; + private selectAuth?: ($step: PgSelectStep) => void; // TODO: make a public interface for this information /** @@ -273,7 +389,9 @@ export class PgResource< */ public sqlPartitionByIndex: SQL | null = null; - public readonly parameters: TParameters; + public readonly parameters: [TParameters] extends [never] + ? never + : ReadonlyArray; public readonly description: string | undefined; public readonly isUnique: boolean; public readonly isMutation: boolean; @@ -291,7 +409,7 @@ export class PgResource< */ public readonly isVirtual: boolean; - public extensions: Partial | undefined; + public extensions: Partial | undefined; /** * @param from - the SQL for the `FROM` clause (without any @@ -329,7 +447,7 @@ export class PgResource< this.identifier = identifier ?? name; this.from = from; this.uniques = uniques ?? ([] as never); - this.parameters = parameters as TParameters; + this.parameters = parameters ?? ([] as never); this.description = description; this.isUnique = !!isUnique; this.sqlPartitionByIndex = sqlPartitionByIndex ?? null; @@ -370,21 +488,21 @@ export class PgResource< * type/relations/etc. */ static alternativeResourceOptions< - TCodec extends PgCodec, - const TNewUniques extends ReadonlyArray< - PgResourceUnique> - >, + TCodec extends AnyPgCodec, + const TNewUniques extends PgResourceUnique>, const TNewName extends string, >( - baseOptions: PgResourceOptions, + baseOptions: PgResourceOptions, overrideOptions: { name: TNewName; identifier?: string; from: SQL; - uniques?: TNewUniques; - extensions?: PgResourceExtensions; + uniques?: [TNewUniques] extends [never] + ? never + : ReadonlyArray; + extensions?: DataplanPg.PgResourceExtensions; }, - ): PgResourceOptions { + ): PgResourceOptions { const { name, identifier, from, uniques, extensions } = overrideOptions; const { codec, executor, selectAuth } = baseOptions; return { @@ -394,7 +512,6 @@ export class PgResource< identifier, from, uniques, - parameters: undefined, extensions, selectAuth, }; @@ -407,11 +524,9 @@ export class PgResource< * type/relations/etc but pull their rows from functions. */ static functionResourceOptions< - TCodec extends PgCodec, - const TNewParameters extends readonly PgResourceParameter[], - const TNewUniques extends ReadonlyArray< - PgResourceUnique> - >, + TCodec extends AnyPgCodec, + const TNewParameters extends AnyPgResourceParameter, + const TNewUniques extends PgResourceUnique>, const TNewName extends string, >( baseOptions: Pick< @@ -518,25 +633,30 @@ export class PgResource< } public getRelation< - TRelationName extends keyof GetPgRegistryCodecRelations, + TRelationName extends PgCodecRelationConfigName< + GetPgRegistryCodecRelationConfigs + >, >( name: TRelationName, ): GetPgRegistryCodecRelations[TRelationName] { return this.getRelations()[name]; } - public resolveVia( - via: PgCodecAttributeVia, - attr: string, - ): PgCodecAttributeViaExplicit { + public resolveVia< + TRelationName extends PgCodecRelationConfigName< + GetPgRegistryCodecRelationConfigs + >, + TAttribute extends string, + >( + via: PgCodecAttributeVia, + attr: TAttribute, + ): PgCodecAttributeViaExplicit { if (!via) { throw new Error("No via to resolve"); } if (typeof via === "string") { // Check - const relation = this.getRelation(via) as unknown as - | PgCodecRelation - | undefined; + const relation = this.getRelation(via); if (!relation) { throw new Error(`Unknown relation '${via}' in ${this}`); } @@ -554,20 +674,20 @@ export class PgResource< // PERF: this needs optimization. public getReciprocal< TOtherCodec extends GetPgRegistryCodecs, - TOtherRelationName extends keyof GetPgRegistryCodecRelations< + TOtherRelationName extends GetPgRegistryCodecRelationConfigs< TRegistry, TOtherCodec - >, + >["name"], >( otherCodec: TOtherCodec, otherRelationName: TOtherRelationName, ): | [ - relationName: keyof GetPgRegistryCodecRelations, - relation: GetPgRegistryCodecRelations< + relationName: GetPgRegistryCodecRelationConfigs< TRegistry, TCodec - >[keyof GetPgRegistryCodecRelations], + >["name"], + relation: GetPgRegistryCodecRelationConfigs, ] | null { if (this.parameters) { @@ -577,10 +697,7 @@ export class PgResource< } const otherRelation = this.registry.pgRelations[otherCodec.name]?.[otherRelationName]; - const relations = this.getRelations() as unknown as Record< - string, - PgCodecRelation - >; + const relations = this.getRelations(); const reciprocal = Object.entries(relations).find( ([_relationName, relation]) => { if (relation.remoteResource.codec !== otherCodec) { @@ -599,16 +716,35 @@ export class PgResource< return true; }, ); + + // TODO: this is still a bit tricky, :-(, but we're almost here! return (reciprocal as [any, any]) || null; } - public get( - spec: PlanByUniques, TUniques>, + public get< + TSelectSinglePlanOptions extends PgSelectSinglePlanOptions, + TSpec extends Expand, TUniques>>, + >( + spec: TSpec, // This is internal, it's an optimisation we can use but you shouldn't. - _internalOptionsDoNotPass?: PgSelectSinglePlanOptions, - ): GetPgCodecAttributes extends PgCodecAttributes - ? PgSelectSingleStep - : PgClassExpressionStep { + _internalOptionsDoNotPass?: TSelectSinglePlanOptions, + ): PgSelectSingleStep; + public get< + TSelectSinglePlanOptions extends PgSelectSinglePlanOptions, + TSpec extends Expand, TUniques>>, + >( + spec: TSpec, + // This is internal, it's an optimisation we can use but you shouldn't. + _internalOptionsDoNotPass?: TSelectSinglePlanOptions, + ): PgClassExpressionStep; + public get< + TSelectSinglePlanOptions extends PgSelectSinglePlanOptions, + TSpec extends Expand, TUniques>>, + >( + spec: TSpec, + // This is internal, it's an optimisation we can use but you shouldn't. + _internalOptionsDoNotPass?: TSelectSinglePlanOptions, + ): PgClassExpressionStep | PgSelectSingleStep { if (this.parameters) { throw new Error( ".get() cannot be used with functional resources; please use .execute()", @@ -617,12 +753,10 @@ export class PgResource< if (!spec) { throw new Error(`Cannot ${this}.get without a valid spec`); } - const keys = Object.keys(spec) as ReadonlyArray as ReadonlyArray< - keyof GetPgCodecAttributes - >; + const keys = Object.keys(spec); if ( - !this.uniques.some((uniq) => - uniq.attributes.every((key) => keys.includes(key as any)), + !this.uniques.some((uniq: AnyPgResourceUnique) => + uniq.attributes.every((key) => keys.includes(key)), ) ) { throw new Error( @@ -633,12 +767,13 @@ export class PgResource< )}). Did you mean to call .find() instead?`, ); } - return this.find(spec).single(_internalOptionsDoNotPass) as any; + + return this.find(spec).single(_internalOptionsDoNotPass); } public find( spec: { - [key in keyof GetPgCodecAttributes]?: + [attribute in PgCodecAttributes as PgCodecAttributeName]?: | ExecutableStep | string | number; @@ -652,10 +787,10 @@ export class PgResource< if (!this.codec.attributes) { throw new Error("Cannot call find if there's no attributes"); } - const attributes = this.codec.attributes as NonNullable< - GetPgCodecAttributes + const attributes = this.codec.attributes; + const keys = Object.keys(spec) as Array< + PgCodecAttributeName> >; - const keys = Object.keys(spec); /* as Array*/ const invalidKeys = keys.filter((key) => attributes[key] == null); if (invalidKeys.length > 0) { throw new Error( @@ -696,7 +831,7 @@ export class PgResource< matches: (alias: SQL) => typeof attribute.expression === "function" ? attribute.expression(alias) - : sql`${alias}.${sql.identifier(key as string)}`, + : sql`${alias}.${sql.identifier(key)}`, }; }); return pgSelect({ resource: this, identifiers }); @@ -797,7 +932,9 @@ export class PgResource< // Every column in a primary key is non-nullable; so just see if one is null const pk = this.uniques.find((u) => u.isPrimary); const nonNullableAttribute = this.codec.attributes - ? Object.entries(this.codec.attributes).find( + ? Object.entries( + this.codec.attributes as Record, + ).find( ([_attributeName, spec]) => !spec.via && !spec.expression && spec.notNull, )?.[0] @@ -822,69 +959,46 @@ export class PgResource< } exportAs("@dataplan/pg", PgResource, "PgResource"); +export interface AnyPgRegistryBuilder + extends PgRegistryBuilder {} +export interface EmptyRegistryBuilder + extends PgRegistryBuilder {} +export interface DefaultRegistryBuilder + extends PgRegistryBuilder< + DefaultPgCodec, + DefaultPgResourceOptions, + DefaultPgCodecRelationConfig + > {} + export interface PgRegistryBuilder< - TCodecs extends { - [name in string]: PgCodec< - name, - PgCodecAttributes | undefined, - any, - any, - any, - any, - any - >; - }, - TResources extends { - [name in string]: PgResourceOptions< - name, - PgCodec, - ReadonlyArray>, - readonly PgResourceParameter[] | undefined - >; - }, - TRelations extends { - [codecName in keyof TCodecs]?: { - [relationName in string]: PgCodecRelationConfig< - PgCodec, - PgResourceOptions - >; - }; - }, + TCodecs extends AnyPgCodec, + TResourceOptions extends AnyPgResourceOptions, + TRelationConfigs extends AnyPgCodecRelationConfig, > { getRegistryConfig(): PgRegistryConfig< - Expand, - Expand, - Expand + TCodecs, + TResourceOptions, + TRelationConfigs >; - addCodec( + addCodec( codec: TCodec, - ): PgRegistryBuilder< - TCodecs & { - [name in TCodec["name"]]: TCodec; - }, - TResources, - TRelations - >; + ): PgRegistryBuilder; - addResource>( - resource: TResource, + addResource( + resource: TResourceOption, ): PgRegistryBuilder< - TCodecs & { - [name in TResource["codec"]["name"]]: TResource["codec"]; - }, - TResources & { - [name in TResource["name"]]: TResource; - }, - TRelations + TCodecs | PgResourceOptionCodec, + TResourceOptions | TResourceOption, + TRelationConfigs >; addRelation< - TCodec extends PgCodec, + TCodec extends AnyPgCodec, const TCodecRelationName extends string, - const TRemoteResource extends PgResourceOptions, + const TRemoteResource extends AnyPgResourceOptions, const TCodecRelation extends Omit< - PgCodecRelationConfig, - "localCodec" | "remoteResourceOptions" + PgCodecRelationConfig, + "localCodec" | "remoteResourceOptions" | "name" >, >( codec: TCodec, @@ -893,52 +1007,32 @@ export interface PgRegistryBuilder< relation: TCodecRelation, ): PgRegistryBuilder< TCodecs, - TResources, - TRelations & { - [codecName in TCodec["name"]]: { - [relationName in TCodecRelationName]: TCodecRelation & { - localCodec: TCodec; - remoteResourceOptions: TRemoteResource; - }; - }; - } + TResourceOptions, + | TRelationConfigs + | (TCodecRelation & { + name: TCodecRelationName; + localCodec: TCodec; + remoteResourceOptions: TRemoteResource; + }) >; - build(): PgRegistry, Expand, Expand>; + build(): PgRegistry; } export function makeRegistry< - TCodecs extends { - [name in string]: PgCodec< - name, - PgCodecAttributes | undefined, - any, - any, - any, - any, - any - >; - }, - TResourceOptions extends { - [name in string]: PgResourceOptions< - name, - PgCodec, - ReadonlyArray>>, - readonly PgResourceParameter[] | undefined - >; - }, - TRelations extends { - [codecName in keyof TCodecs]?: { - [relationName in string]: PgCodecRelationConfig< - PgCodec, - PgResourceOptions - >; - }; - }, + TConfig extends PgRegistryConfig< + any, + PgResourceOptions, + AnyPgCodecRelationConfig + >, >( - config: PgRegistryConfig, -): PgRegistry { - const registry: PgRegistry = { + config: TConfig, +): PgRegistry< + PgRegistryConfigCodecs, + PgRegistryConfigResourceOptions, + PgRegistryConfigRelationConfigs +> { + const registry: AnyPgRegistry = { pgCodecs: Object.create(null) as any, pgResources: Object.create(null) as any, pgRelations: Object.create(null) as any, @@ -948,24 +1042,24 @@ export function makeRegistry< Object.defineProperties(registry.pgCodecs, { $exporter$args: { value: [registry] }, $exporter$factory: { - value: (registry: PgRegistry) => registry.pgCodecs, + value: (registry: AnyPgRegistry) => registry.pgCodecs, }, }); Object.defineProperties(registry.pgResources, { $exporter$args: { value: [registry] }, $exporter$factory: { - value: (registry: PgRegistry) => registry.pgResources, + value: (registry: AnyPgRegistry) => registry.pgResources, }, }); Object.defineProperties(registry.pgRelations, { $exporter$args: { value: [registry] }, $exporter$factory: { - value: (registry: PgRegistry) => registry.pgRelations, + value: (registry: AnyPgRegistry) => registry.pgRelations, }, }); let addCodecForbidden = false; - function addCodec(codec: PgCodec): PgCodec { + function addCodec(codec: TCodec): TCodec { if (addCodecForbidden) { throw new Error(`It's too late to call addCodec now`); } @@ -977,19 +1071,22 @@ export function makeRegistry< new: codec, }); throw new Error( - `Codec named '${codecName}' is already registsred; you cannot have two codecs with the same name`, + `Codec named '${codecName}' is already registered; you cannot have two codecs with the same name`, ); } return codec; } else if ((codec as any).$$export || (codec as any).$exporter$factory) { - registry.pgCodecs[codecName as keyof TCodecs] = codec as any; + registry.pgCodecs[codecName] = codec; return codec; } else { // Custom spec, pin it back to the registry - registry.pgCodecs[codecName as keyof TCodecs] = codec as any; + registry.pgCodecs[codecName] = codec; if (codec.attributes) { - const prevCols = codec.attributes as PgCodecAttributes; + const prevCols = codec.attributes as Record< + string, + AnyPgCodecAttribute + >; for (const col of Object.values(prevCols)) { addCodec(col.codec); } @@ -1008,7 +1105,7 @@ export function makeRegistry< Object.defineProperties(codec, { $exporter$args: { value: [registry, codecName] }, $exporter$factory: { - value: (registry: PgRegistry, codecName: string) => + value: (registry: AnyPgRegistry, codecName: string) => registry.pgCodecs[codecName], }, }); @@ -1024,20 +1121,18 @@ export function makeRegistry< addCodec(codecSpec); } - for (const [resourceName, rawConfig] of Object.entries( - config.pgResources, - ) as [keyof TResourceOptions, PgResourceOptions][]) { - const resourceConfig = { + for (const [resourceName, rawConfig] of Object.entries(config.pgResources)) { + const resourceConfig: AnyPgResourceOptions = { ...rawConfig, codec: addCodec(rawConfig.codec), parameters: rawConfig.parameters - ? (rawConfig.parameters as readonly PgResourceParameter[]).map((p) => ({ + ? rawConfig.parameters.map((p) => ({ ...p, codec: addCodec(p.codec), })) : rawConfig.parameters, }; - const resource = new PgResource(registry, resourceConfig) as any; + const resource = new PgResource(registry, resourceConfig); // This is the magic that breaks the circular reference: rather than // building PgResource via a factory we tell the system to just retrieve it @@ -1045,7 +1140,7 @@ export function makeRegistry< Object.defineProperties(resource, { $exporter$args: { value: [registry, resourceName] }, $exporter$factory: { - value: (registry: PgRegistry, resourceName: string) => + value: (registry: AnyPgRegistry, resourceName: string) => registry.pgResources[resourceName], }, }); @@ -1054,19 +1149,13 @@ export function makeRegistry< } // Ensure all the relation codecs are also added - for (const codecName of Object.keys( - config.pgRelations, - ) as (keyof typeof config.pgRelations)[]) { + for (const codecName of Object.keys(config.pgRelations)) { const relations = config.pgRelations[codecName]; if (!relations) { continue; } - for (const relationName of Object.keys( - relations, - ) as (keyof typeof relations)[]) { - const relationConfig = relations![ - relationName - ] as unknown as PgCodecRelationConfig; + for (const relationName of Object.keys(relations)) { + const relationConfig = relations![relationName]; if (relationConfig) { addCodec(relationConfig.localCodec); } @@ -1083,11 +1172,11 @@ export function makeRegistry< * remove the ones that already have resources, then we build resources for the * remainder. */ - const tableLikeCodecsWithoutTableLikeResources = new Set(); - const walkCodec = ( - codec: PgCodec, + const tableLikeCodecsWithoutTableLikeResources = new Set(); + const walkCodec = ( + codec: TCodec, isAccessibleViaAttribute = false, - seen = new Set(), + seen = new Set(), ) => { if (seen.has(codec)) { return; @@ -1102,7 +1191,9 @@ export function makeRegistry< tableLikeCodecsWithoutTableLikeResources.add(codec); } if (codec.attributes) { - for (const col of Object.values(codec.attributes)) { + for (const col of Object.values( + codec.attributes as Record, + )) { if (isAccessibleViaAttribute) { walkCodec(col.codec, isAccessibleViaAttribute, seen); } else { @@ -1136,12 +1227,11 @@ export function makeRegistry< // Now add resources for the table-like codecs that don't have them already for (const codec of tableLikeCodecsWithoutTableLikeResources) { if (codec.executor) { - const resourceName = `frmcdc_${codec.name}` as keyof TResourceOptions & - string; + const resourceName = `frmcdc_${codec.name}`; const resource = new PgResource(registry, { name: resourceName, executor: codec.executor, - from: sql`(select 1/0 /* codec-only resource; should not select directly */)`, + from: sql`(select 1/0 /* codec-only resource; should not select directly */)` as any, codec, identifier: resourceName, isVirtual: true, @@ -1150,12 +1240,12 @@ export function makeRegistry< behavior: "-*", }, }, - }) as any; + }); Object.defineProperties(resource, { $exporter$args: { value: [registry, resourceName] }, $exporter$factory: { - value: (registry: PgRegistry, resourceName: string) => + value: (registry: AnyPgRegistry, resourceName: string) => registry.pgResources[resourceName], }, }); @@ -1164,9 +1254,7 @@ export function makeRegistry< } } - for (const codecName of Object.keys( - config.pgRelations, - ) as (keyof typeof config.pgRelations)[]) { + for (const codecName of Object.keys(config.pgRelations)) { const relations = config.pgRelations[codecName]; if (!relations) { continue; @@ -1178,34 +1266,30 @@ export function makeRegistry< Object.defineProperties(builtRelations, { $exporter$args: { value: [registry, codecName] }, $exporter$factory: { - value: (registry: PgRegistry, codecName: string) => + value: (registry: AnyPgRegistry, codecName: string) => registry.pgRelations[codecName], }, }); - for (const relationName of Object.keys( - relations, - ) as (keyof typeof relations)[]) { - const relationConfig = relations![ - relationName - ] as unknown as PgCodecRelationConfig; + for (const relationName of Object.keys(relations)) { + const relationConfig = relations[relationName]; if (!relationConfig) { continue; } const { localCodec, remoteResourceOptions, ...rest } = relationConfig; const builtRelation = { - ...(rest as any), + ...rest, localCodec, remoteResource: registry.pgResources[remoteResourceOptions.name], - } as PgCodecRelation; + }; // Tell the system to read the built relation from the registry Object.defineProperties(builtRelation, { $exporter$args: { value: [registry, codecName, relationName] }, $exporter$factory: { value: ( - registry: PgRegistry, + registry: AnyPgRegistry, codecName: string, relationName: string, ) => registry.pgRelations[codecName][relationName], @@ -1224,16 +1308,24 @@ export function makeRegistry< } exportAs("@dataplan/pg", makeRegistry, "makeRegistry"); -function validateRelations(registry: PgRegistry): void { +function validateRelations< + TCodecs extends AnyPgCodec, + TResourceOptions extends AnyPgResourceOptions, + TRelationConfigs extends AnyPgCodecRelationConfig, +>(registry: PgRegistry): void { // PERF: skip this if not isDev? - const reg = registry as PgRegistry; + const reg = registry; - for (const codec of Object.values(reg.pgCodecs)) { + for (const codec of Object.values(reg.pgCodecs) as Array) { // Check that all the `via` and `identicalVia` match actual relations. - const relationKeys = Object.keys(reg.pgRelations[codec.name] ?? {}); + const relationKeys = Object.keys( + reg.pgRelations[codec.name as PgCodecName] ?? {}, + ); if (codec.attributes) { - Object.entries(codec.attributes).forEach(([attributeName, col]) => { + Object.entries( + codec.attributes as Record, + ).forEach(([attributeName, col]) => { const { via, identicalVia } = col; if (via != null) { if (typeof via === "string") { @@ -1270,15 +1362,14 @@ function validateRelations(registry: PgRegistry): void { } } -// eslint-disable-next-line @typescript-eslint/ban-types -export function makeRegistryBuilder(): PgRegistryBuilder<{}, {}, {}> { - const registryConfig: PgRegistryConfig = { +export function makeRegistryBuilder(): EmptyRegistryBuilder { + const registryConfig: AnyPgRegistryConfig = { pgCodecs: Object.create(null), pgResources: Object.create(null), pgRelations: Object.create(null), }; - const builder: PgRegistryBuilder = { + const builder: AnyPgRegistryBuilder = { getRegistryConfig() { return registryConfig; }, @@ -1306,7 +1397,9 @@ export function makeRegistryBuilder(): PgRegistryBuilder<{}, {}, {}> { this.addCodec(codec.rangeOfCodec); } if (codec.attributes) { - for (const col of Object.values(codec.attributes)) { + for (const col of Object.values( + codec.attributes as Record, + )) { this.addCodec(col.codec); } } @@ -1314,9 +1407,7 @@ export function makeRegistryBuilder(): PgRegistryBuilder<{}, {}, {}> { }, addResource(resource) { - const existing = registryConfig.pgResources[resource.name] as - | PgResourceOptions - | undefined; + const existing = registryConfig.pgResources[resource.name]; if (existing) { if (existing !== resource) { throw new Error( @@ -1353,11 +1444,9 @@ export function makeRegistryBuilder(): PgRegistryBuilder<{}, {}, {}> { registryConfig.pgRelations[localCodec.name][relationName] = { localCodec, remoteResourceOptions, + name: relationName, ...relation, - } as PgCodecRelationConfig< - PgCodecWithAttributes, - PgResourceOptions - >; + }; return builder; }, @@ -1374,14 +1463,29 @@ export function makeRegistryBuilder(): PgRegistryBuilder<{}, {}, {}> { exportAs("@dataplan/pg", makeRegistryBuilder, "makeRegistryBuilder"); export function makePgResourceOptions< - const TResourceOptions extends PgResourceOptions, ->(options: TResourceOptions) { + const TName extends string, + const TCodec extends AnyPgCodec, + const TAttributes extends PgCodecAttributes, + const TUniques extends PgResourceUnique< + TAttributes[keyof TAttributes] + > = never, + const TParameters extends AnyPgResourceParameter = never, +>( + options: PgResourceOptions, +): PgResourceOptions { return options; } exportAs("@dataplan/pg", makePgResourceOptions, "makePgResourceOptions"); -function printResourceFrom(resource: PgResourceOptions): string { +function printResourceFrom< + TResource extends PgResourceOptions< + any, + AnyPgCodec, + any, + AnyPgResourceParameter + >, +>(resource: TResource): string { if (typeof resource.from === "function") { return `a function accepting ${resource.parameters ?.length} parameters and returning SQL type '${ diff --git a/grafast/dataplan-pg/src/examples/exampleSchema.ts b/grafast/dataplan-pg/src/examples/exampleSchema.ts index 742f209105..1187d18abd 100644 --- a/grafast/dataplan-pg/src/examples/exampleSchema.ts +++ b/grafast/dataplan-pg/src/examples/exampleSchema.ts @@ -69,7 +69,6 @@ import sql from "pg-sql2"; import { inspect } from "util"; import type { - GetPgResourceRelations, PgCodecAttribute, PgCodecAttributeVia, PgConditionStep, @@ -79,11 +78,15 @@ import type { WithPgClient, } from "../"; import type { NodePostgresPgClient, PgSubscriber } from "../adaptors/pg.js"; -import { listOfCodec } from "../codecs.js"; +import { listOfCodec, PgCodecAttributeName } from "../codecs.js"; import { + AnyPgResource, makePgResourceOptions, makeRegistry, makeRegistryBuilder, + PgResourceCodec, + PgResourceName, + PgResourceUniques, } from "../datasource.js"; import { enumCodec, @@ -107,7 +110,16 @@ import { recordCodec, TYPES, } from "../index.js"; -import type { GetPgResourceAttributes, PgCodec } from "../interfaces"; +import type { + AnyPgCodec, + GetPgResourceAttributeMap, + GetPgResourceAttributes, + GetPgResourceRelationConfigs, + PgCodec, + PgCodecAttributeMap, + PgCodecAttributes, + PgCodecRelationConfigName, +} from "../interfaces"; import { PgPageInfoStep } from "../steps/pgPageInfo.js"; import type { PgPolymorphicTypeMap } from "../steps/pgPolymorphic.js"; import type { PgSelectParsedCursorStep } from "../steps/pgSelect.js"; @@ -118,6 +130,10 @@ import { WithPgClientStep, withPgClientTransaction, } from "../steps/withPgClient.js"; +import { + AnyPgSelectSingleStep, + PgSelectSingleStepResource, +} from "../steps/pgSelectSingle"; const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); @@ -197,19 +213,28 @@ export function makeExampleSchema( ) => { const col = < TOptions extends { - codec: PgCodec; + codec: AnyPgCodec; notNull?: boolean; - expression?: PgCodecAttribute["expression"]; - via?: PgCodecAttributeVia; - identicalVia?: PgCodecAttributeVia; + expression?: PgCodecAttribute["expression"]; + via?: PgCodecAttributeVia; + identicalVia?: PgCodecAttributeVia; }, >( options: TOptions, - ): PgCodecAttribute => { + ): PgCodecAttribute< + any, + TOptions extends { codec: infer U } ? U : never, + TOptions extends { notNull: infer U } + ? U extends boolean + ? U + : false + : false + > => { const { notNull, codec, expression, via, identicalVia } = options; return { + name: undefined, codec: codec as TOptions extends { codec: infer U } ? U : never, - notNull: !!notNull, + notNull: !!notNull as any, expression, via, identicalVia, @@ -294,7 +319,6 @@ export function makeExampleSchema( from: (...args) => sql`app_public.forum_names_array(${sqlFromArgDigests(args)})`, name: "forum_names_array", - parameters: [], isUnique: true, // No setof }); @@ -305,7 +329,6 @@ export function makeExampleSchema( from: (...args) => sql`app_public.forum_names_cases(${sqlFromArgDigests(args)})`, name: "forum_names_cases", - parameters: [], }); const forumsUniqueAuthorCountResourceOptions = makePgResourceOptions({ @@ -394,7 +417,6 @@ export function makeExampleSchema( sql`app_public.featured_messages(${sqlFromArgDigests(args)})`, returnsSetof: true, returnsArray: false, - parameters: [], }); const forumsFeaturedMessagesResourceOptions = @@ -423,7 +445,6 @@ export function makeExampleSchema( sql`app_public.random_user_array(${sqlFromArgDigests(args)})`, returnsArray: true, returnsSetof: false, - parameters: [], }, ); @@ -434,7 +455,6 @@ export function makeExampleSchema( sql`app_public.random_user_array_set(${sqlFromArgDigests(args)})`, returnsSetof: true, returnsArray: true, - parameters: [], }); const forumsMessagesListSetResourceOptions = @@ -444,7 +464,6 @@ export function makeExampleSchema( sql`app_public.forums_messages_list_set(${sqlFromArgDigests( args, )})`, - parameters: [], returnsArray: true, returnsSetof: true, extensions: { @@ -726,7 +745,7 @@ export function makeExampleSchema( const itemAttributes = { id: col({ codec: TYPES.int, notNull: true, identicalVia: "item" }), - type: col({ codec: TYPES.text, notNull: true, via: "item" }), + type: col({ codec: itemTypeEnumCodec, notNull: true, via: "item" }), type2: col({ codec: enumTableItemTypeEnumCodec, notNull: true, @@ -1291,8 +1310,8 @@ export function makeExampleSchema( "item", relationalItemsResourceOptions, { - localAttributes: [`id`] as const, - remoteAttributes: [`id`] as const, + localAttributes: [`id`], + remoteAttributes: [`id`], isUnique: true, }, ) @@ -1301,14 +1320,14 @@ export function makeExampleSchema( "parent", relationalItemsResourceOptions, { - localAttributes: [`parent_id`] as const, - remoteAttributes: [`id`] as const, + localAttributes: [`parent_id`], + remoteAttributes: [`id`], isUnique: true, }, ) .addRelation(relationalTopicsCodec, "author", personResourceOptions, { - localAttributes: [`author_id`] as const, - remoteAttributes: [`person_id`] as const, + localAttributes: [`author_id`], + remoteAttributes: [`person_id`], isUnique: true, }) @@ -1317,8 +1336,8 @@ export function makeExampleSchema( "item", relationalItemsResourceOptions, { - localAttributes: [`id`] as const, - remoteAttributes: [`id`] as const, + localAttributes: [`id`], + remoteAttributes: [`id`], isUnique: true, }, ) @@ -1327,14 +1346,14 @@ export function makeExampleSchema( "parent", relationalItemsResourceOptions, { - localAttributes: [`parent_id`] as const, - remoteAttributes: [`id`] as const, + localAttributes: [`parent_id`], + remoteAttributes: [`id`], isUnique: true, }, ) .addRelation(relationalPostsCodec, "author", personResourceOptions, { - localAttributes: [`author_id`] as const, - remoteAttributes: [`person_id`] as const, + localAttributes: [`author_id`], + remoteAttributes: [`person_id`], isUnique: true, }) .addRelation( @@ -1342,8 +1361,8 @@ export function makeExampleSchema( "commentable", relationalCommentableResourceOptions, { - localAttributes: [`id`] as const, - remoteAttributes: [`id`] as const, + localAttributes: [`id`], + remoteAttributes: [`id`], isUnique: true, }, ) @@ -1353,8 +1372,8 @@ export function makeExampleSchema( "item", relationalItemsResourceOptions, { - localAttributes: [`id`] as const, - remoteAttributes: [`id`] as const, + localAttributes: [`id`], + remoteAttributes: [`id`], isUnique: true, }, ) @@ -1363,14 +1382,14 @@ export function makeExampleSchema( "parent", relationalItemsResourceOptions, { - localAttributes: [`parent_id`] as const, - remoteAttributes: [`id`] as const, + localAttributes: [`parent_id`], + remoteAttributes: [`id`], isUnique: true, }, ) .addRelation(relationalDividersCodec, "author", personResourceOptions, { - localAttributes: [`author_id`] as const, - remoteAttributes: [`person_id`] as const, + localAttributes: [`author_id`], + remoteAttributes: [`person_id`], isUnique: true, }) .addRelation( @@ -1378,8 +1397,8 @@ export function makeExampleSchema( "item", relationalItemsResourceOptions, { - localAttributes: [`id`] as const, - remoteAttributes: [`id`] as const, + localAttributes: [`id`], + remoteAttributes: [`id`], isUnique: true, }, ) @@ -1388,8 +1407,8 @@ export function makeExampleSchema( "parent", relationalItemsResourceOptions, { - localAttributes: [`parent_id`] as const, - remoteAttributes: [`id`] as const, + localAttributes: [`parent_id`], + remoteAttributes: [`id`], isUnique: true, }, ) @@ -1398,8 +1417,8 @@ export function makeExampleSchema( "author", personResourceOptions, { - localAttributes: [`author_id`] as const, - remoteAttributes: [`person_id`] as const, + localAttributes: [`author_id`], + remoteAttributes: [`person_id`], isUnique: true, }, ) @@ -1408,8 +1427,8 @@ export function makeExampleSchema( "commentable", relationalCommentableResourceOptions, { - localAttributes: [`id`] as const, - remoteAttributes: [`id`] as const, + localAttributes: [`id`], + remoteAttributes: [`id`], isUnique: true, }, ) @@ -1418,8 +1437,8 @@ export function makeExampleSchema( "item", relationalItemsResourceOptions, { - localAttributes: [`id`] as const, - remoteAttributes: [`id`] as const, + localAttributes: [`id`], + remoteAttributes: [`id`], isUnique: true, }, ) @@ -1428,8 +1447,8 @@ export function makeExampleSchema( "parent", relationalItemsResourceOptions, { - localAttributes: [`parent_id`] as const, - remoteAttributes: [`id`] as const, + localAttributes: [`parent_id`], + remoteAttributes: [`id`], isUnique: true, }, ) @@ -1438,8 +1457,8 @@ export function makeExampleSchema( "author", personResourceOptions, { - localAttributes: [`author_id`] as const, - remoteAttributes: [`person_id`] as const, + localAttributes: [`author_id`], + remoteAttributes: [`person_id`], isUnique: true, }, ) @@ -1448,8 +1467,8 @@ export function makeExampleSchema( "commentable", relationalCommentableResourceOptions, { - localAttributes: [`id`] as const, - remoteAttributes: [`id`] as const, + localAttributes: [`id`], + remoteAttributes: [`id`], isUnique: true, }, ) @@ -1460,8 +1479,8 @@ export function makeExampleSchema( relationalItemsResourceOptions, { isUnique: true, - localAttributes: ["parent_id"] as const, - remoteAttributes: ["id"] as const, + localAttributes: ["parent_id"], + remoteAttributes: ["id"], }, ) .addRelation( @@ -1470,22 +1489,22 @@ export function makeExampleSchema( relationalItemsResourceOptions, { isUnique: false, - localAttributes: ["id"] as const, - remoteAttributes: ["parent_id"] as const, + localAttributes: ["id"], + remoteAttributes: ["parent_id"], }, ) .addRelation(relationalItemsCodec, "author", personResourceOptions, { isUnique: true, - localAttributes: ["author_id"] as const, - remoteAttributes: ["person_id"] as const, + localAttributes: ["author_id"], + remoteAttributes: ["person_id"], }) .addRelation( relationalItemsCodec, "topic", relationalTopicsResourceOptions, { - localAttributes: [`id`] as const, - remoteAttributes: [`id`] as const, + localAttributes: [`id`], + remoteAttributes: [`id`], isUnique: true, // reciprocal: 'item', }, @@ -1495,8 +1514,8 @@ export function makeExampleSchema( "post", relationalPostsResourceOptions, { - localAttributes: [`id`] as const, - remoteAttributes: [`id`] as const, + localAttributes: [`id`], + remoteAttributes: [`id`], isUnique: true, // reciprocal: 'item', }, @@ -1506,8 +1525,8 @@ export function makeExampleSchema( "divider", relationalDividersResourceOptions, { - localAttributes: [`id`] as const, - remoteAttributes: [`id`] as const, + localAttributes: [`id`], + remoteAttributes: [`id`], isUnique: true, // reciprocal: 'item', }, @@ -1517,8 +1536,8 @@ export function makeExampleSchema( "checklist", relationalChecklistsResourceOptions, { - localAttributes: [`id`] as const, - remoteAttributes: [`id`] as const, + localAttributes: [`id`], + remoteAttributes: [`id`], isUnique: true, // reciprocal: 'item', }, @@ -1528,8 +1547,8 @@ export function makeExampleSchema( "checklistItem", relationalChecklistItemsResourceOptions, { - localAttributes: [`id`] as const, - remoteAttributes: [`id`] as const, + localAttributes: [`id`], + remoteAttributes: [`id`], isUnique: true, // reciprocal: 'item', }, @@ -1540,8 +1559,8 @@ export function makeExampleSchema( "post", relationalPostsResourceOptions, { - localAttributes: [`id`] as const, - remoteAttributes: [`id`] as const, + localAttributes: [`id`], + remoteAttributes: [`id`], isUnique: true, // reciprocal: 'item', }, @@ -1551,8 +1570,8 @@ export function makeExampleSchema( "checklist", relationalChecklistsResourceOptions, { - localAttributes: [`id`] as const, - remoteAttributes: [`id`] as const, + localAttributes: [`id`], + remoteAttributes: [`id`], isUnique: true, // reciprocal: 'item', }, @@ -1562,26 +1581,26 @@ export function makeExampleSchema( "checklistItem", relationalChecklistItemsResourceOptions, { - localAttributes: [`id`] as const, - remoteAttributes: [`id`] as const, + localAttributes: [`id`], + remoteAttributes: [`id`], isUnique: true, // reciprocal: 'item', }, ) .addRelation(unionItemsCodec, "topic", unionTopicsResourceOptions, { - localAttributes: [`id`] as const, - remoteAttributes: [`id`] as const, + localAttributes: [`id`], + remoteAttributes: [`id`], isUnique: true, }) .addRelation(unionItemsCodec, "post", unionPostsResource, { - localAttributes: [`id`] as const, - remoteAttributes: [`id`] as const, + localAttributes: [`id`], + remoteAttributes: [`id`], isUnique: true, }) .addRelation(unionItemsCodec, "divider", unionDividersResourceOptions, { - localAttributes: [`id`] as const, - remoteAttributes: [`id`] as const, + localAttributes: [`id`], + remoteAttributes: [`id`], isUnique: true, }) .addRelation( @@ -1589,8 +1608,8 @@ export function makeExampleSchema( "checklist", unionChecklistsResourceOptions, { - localAttributes: [`id`] as const, - remoteAttributes: [`id`] as const, + localAttributes: [`id`], + remoteAttributes: [`id`], isUnique: true, }, ) @@ -1599,8 +1618,8 @@ export function makeExampleSchema( "checklistItem", unionChecklistItemsResourceOptions, { - localAttributes: [`id`] as const, - remoteAttributes: [`id`] as const, + localAttributes: [`id`], + remoteAttributes: [`id`], isUnique: true, }, ) @@ -1621,11 +1640,17 @@ export function makeExampleSchema( ], ); - const registry = EXPORTABLE( + const rawRegistry = EXPORTABLE( (makeRegistry, registryConfig) => makeRegistry(registryConfig), [makeRegistry, registryConfig], ); + type RawRegistry = typeof rawRegistry; + + interface Registry extends RawRegistry {} + + const registry: Registry = rawRegistry; + if (Math.random() > 2) { /* * This block includes a rudimentary TypeScript types test - we get a @@ -1665,7 +1690,7 @@ export function makeExampleSchema( const innerPlan = step instanceof __ListTransformStep ? step.getListStep() - : (step as PgSelectStep | PgSelectSingleStep); + : (step as PgSelectStep | PgSelectSingleStep); if ("getClassStep" in innerPlan) { innerPlan.getClassStep().setInliningForbidden(); } else if ("setInliningForbidden" in innerPlan) { @@ -1677,9 +1702,7 @@ export function makeExampleSchema( [__ListTransformStep, options], ); - type ResourceConnectionPlan< - TResource extends PgResource, - > = ConnectionStep< + type ResourceConnectionPlan = ConnectionStep< PgSelectSingleStep, PgSelectParsedCursorStep, PgSelectStep, @@ -1727,48 +1750,103 @@ export function makeExampleSchema( }, } = registry; - type MessageConnectionStep = ResourceConnectionPlan; - type MessageStep = PgSelectSingleStep; - type UserStep = PgSelectSingleStep; - type ForumStep = PgSelectSingleStep; - type PersonStep = PgSelectSingleStep; - type PersonBookmarkStep = PgSelectSingleStep; - type PostStep = PgSelectSingleStep; - type CommentStep = PgSelectSingleStep; - type SingleTableItemsStep = PgSelectStep; - type SingleTableItemStep = PgSelectSingleStep< - typeof singleTableItemsResource - >; - type RelationalItemsStep = PgSelectStep; - type RelationalItemStep = PgSelectSingleStep; - type RelationalTopicStep = PgSelectSingleStep< - typeof relationalTopicsResource - >; - type RelationalPostStep = PgSelectSingleStep; - type RelationalDividerStep = PgSelectSingleStep< - typeof relationalDividersResource - >; - type RelationalChecklistStep = PgSelectSingleStep< - typeof relationalChecklistsResource - >; - type RelationalChecklistItemStep = PgSelectSingleStep< - typeof relationalChecklistItemsResource - >; - type UnionItemsStep = PgSelectStep; - type UnionItemStep = PgSelectSingleStep; - type UnionTopicStep = PgSelectSingleStep; - type UnionPostStep = PgSelectSingleStep; - type UnionDividerStep = PgSelectSingleStep; - type UnionChecklistStep = PgSelectSingleStep; - type UnionChecklistItemStep = PgSelectSingleStep< - typeof unionChecklistItemsResource - >; - type RelationalCommentablesStep = PgSelectStep< - typeof relationalCommentableResource - >; - type RelationalCommentableStep = PgSelectSingleStep< - typeof relationalCommentableResource - >; + type unionEntityCodec = typeof unionEntityCodec; + interface UnionEntityCodec extends unionEntityCodec {} + + type messages = typeof messageResource; + interface MessageResource extends messages {} + + type MessageConnectionStep = ResourceConnectionPlan; + + type users = typeof userResource; + interface UserResource extends users {} + + type forums = typeof forumResource; + interface ForumResource extends forums {} + + type people = typeof personResource; + interface PersonResource extends people {} + + type person_bookmarks = typeof personBookmarksResource; + interface PersonBookmarkResource extends person_bookmarks {} + + type posts = typeof postResource; + interface PostResource extends posts {} + + type comments = typeof commentResource; + interface CommentResource extends comments {} + + type single_table_items = typeof singleTableItemsResource; + interface SingleTableItemResource extends single_table_items {} + + type relational_items = typeof relationalItemsResource; + interface RelationalItemResource extends relational_items {} + + type relational_topics = typeof relationalTopicsResource; + interface RelationalTopicResource extends relational_topics {} + + type relational_posts = typeof relationalPostsResource; + interface RelationalPostResource extends relational_posts {} + + type relational_dividers = typeof relationalDividersResource; + interface RelationalDividerResource extends relational_dividers {} + + type relational_checklists = typeof relationalChecklistsResource; + interface RelationalChecklistResource extends relational_checklists {} + + type relational_checklist_items = typeof relationalChecklistItemsResource; + interface RelationalChecklistItemResource + extends relational_checklist_items {} + + type union_items = typeof unionItemsResource; + interface UnionItemResource extends union_items {} + + type union_topics = typeof unionTopicsResource; + interface UnionTopicResource extends union_topics {} + + type union_posts = typeof unionPostsResource; + interface UnionPostResource extends union_posts {} + + type union_dividers = typeof unionDividersResource; + interface UnionDividerResource extends union_dividers {} + + type union_checklists = typeof unionChecklistsResource; + interface UnionChecklistResource extends union_checklists {} + + type union_checklist_items = typeof unionChecklistItemsResource; + interface UnionChecklistItemResource extends union_checklist_items {} + + type relational_commentables = typeof relationalCommentableResource; + interface RelationalCommentableResource extends relational_commentables {} + + type MessageStep = PgSelectSingleStep; + type UserStep = PgSelectSingleStep; + type ForumStep = PgSelectSingleStep; + type PersonStep = PgSelectSingleStep; + type PersonBookmarkStep = PgSelectSingleStep; + type PostStep = PgSelectSingleStep; + type CommentStep = PgSelectSingleStep; + type SingleTableItemsStep = PgSelectStep; + type SingleTableItemStep = PgSelectSingleStep; + type RelationalItemsStep = PgSelectStep; + type RelationalItemStep = PgSelectSingleStep; + type RelationalTopicStep = PgSelectSingleStep; + type RelationalPostStep = PgSelectSingleStep; + type RelationalDividerStep = PgSelectSingleStep; + type RelationalChecklistStep = + PgSelectSingleStep; + type RelationalChecklistItemStep = + PgSelectSingleStep; + type UnionItemsStep = PgSelectStep; + type UnionItemStep = PgSelectSingleStep; + type UnionTopicStep = PgSelectSingleStep; + type UnionPostStep = PgSelectSingleStep; + type UnionDividerStep = PgSelectSingleStep; + type UnionChecklistStep = PgSelectSingleStep; + type UnionChecklistItemStep = PgSelectSingleStep; + type RelationalCommentablesStep = PgSelectStep; + type RelationalCommentableStep = + PgSelectSingleStep; //////////////////////////////////////// @@ -1784,8 +1862,10 @@ export function makeExampleSchema( }); function attrField< - TMyResource extends PgResource, - TAttrName extends keyof GetPgResourceAttributes, + TMyResource extends AnyPgResource, + TAttrName extends PgCodecAttributeName< + GetPgResourceAttributes + >, >(attrName: TAttrName, type: GraphQLOutputType) { return { type, @@ -1800,8 +1880,10 @@ export function makeExampleSchema( } function singleRelationField< - TMyResource extends PgResource, - TRelationName extends keyof GetPgResourceRelations, + TMyResource extends AnyPgResource, + TRelationName extends PgCodecRelationConfigName< + GetPgResourceRelationConfigs + >, >(relation: TRelationName, type: GraphQLOutputType) { return { type, @@ -2827,7 +2909,7 @@ export function makeExampleSchema( const relationalItemPolymorphicTypeMap = EXPORTABLE( ( deoptimizeIfAppropriate, - ): PgPolymorphicTypeMap => ({ + ): PgPolymorphicTypeMap => ({ RelationalTopic: { match: (t) => t === "TOPIC", plan: (_, $item) => @@ -2859,7 +2941,7 @@ export function makeExampleSchema( const relationalItemInterface = EXPORTABLE( (pgPolymorphic, relationalItemPolymorphicTypeMap) => - ($item: RelationalItemStep) => + ($item: TStep) => pgPolymorphic( $item, $item.get("type"), @@ -2945,7 +3027,8 @@ export function makeExampleSchema( personResource, postResource, ): PgPolymorphicTypeMap< - PgSelectSingleStep | PgClassExpressionStep, + | PgSelectSingleStep + | PgClassExpressionStep, any>, readonly number[], ListStep > => ({ @@ -2977,10 +3060,10 @@ export function makeExampleSchema( */ const entityUnion = EXPORTABLE( (entityPolymorphicTypeMap, list, pgPolymorphic) => - ( + ( $item: - | PgSelectSingleStep> - | PgClassExpressionStep>, + | PgSelectSingleStep> + | PgClassExpressionStep, ) => pgPolymorphic( $item, @@ -3033,10 +3116,9 @@ export function makeExampleSchema( ) => function plan($person) { const $personId = $person.get("person_id"); - const $items: SingleTableItemsStep = - singleTableItemsResource.find({ - author_id: $personId, - }); + const $items = singleTableItemsResource.find({ + author_id: $personId, + }); deoptimizeIfAppropriate($items); return each($items, singleTableItemInterface); }, @@ -3156,9 +3238,7 @@ export function makeExampleSchema( [fieldName: string]: GrafastFieldConfig< any, any, - PgSelectSingleStep< - PgResource - >, + SingleTableItemStep, any, any >; @@ -3255,8 +3335,27 @@ export function makeExampleSchema( }), }); + interface RelationalItemsLikeStep + extends PgSelectSingleStep< + PgResource< + any, + PgCodec< + any, + PgCodecAttributeMap>, + any, + any, + any, + any, + any + >, + any, + any, + any + > + > {} + const commonRelationalItemFields = < - TStep extends PgSelectSingleStep, + TStep extends RelationalItemsLikeStep, >() => ({ id: attrField("id", GraphQLInt), @@ -3638,7 +3737,7 @@ export function makeExampleSchema( extensions: { grafast: { applyPlan: EXPORTABLE( - () => (step: PgUnionAllStep) => { + () => (step: PgUnionAllStep) => { step.orderBy({ attribute: "cvss_score", direction: "ASC", @@ -3653,7 +3752,7 @@ export function makeExampleSchema( extensions: { grafast: { applyPlan: EXPORTABLE( - () => (step: PgUnionAllStep) => { + () => (step: PgUnionAllStep) => { step.orderBy({ attribute: "cvss_score", direction: "DESC", @@ -4310,7 +4409,7 @@ export function makeExampleSchema( pgCodec: TYPES.text, name: "query", }, - ]) as PgSelectStep; + ]) as PgSelectStep; deoptimizeIfAppropriate($step); return each($step, ($item) => entityUnion($item as any)); }, @@ -4708,7 +4807,15 @@ export function makeExampleSchema( type PgRecord> = PgClassExpressionStep< - PgCodec, any, any, any, any, any>, + PgCodec< + any, + GetPgResourceAttributeMap, + any, + any, + any, + any, + any + >, TResource >; diff --git a/grafast/dataplan-pg/src/filters/pgBooleanFilter.ts b/grafast/dataplan-pg/src/filters/pgBooleanFilter.ts index a43f62419d..17579d4bf0 100644 --- a/grafast/dataplan-pg/src/filters/pgBooleanFilter.ts +++ b/grafast/dataplan-pg/src/filters/pgBooleanFilter.ts @@ -2,7 +2,7 @@ import type { ExecutableStep } from "grafast"; import { ModifierStep } from "grafast"; import type { SQL } from "pg-sql2"; -import type { PgCodec, PgConditionLikeStep } from "../interfaces.js"; +import type { AnyPgCodec, PgCodec, PgConditionLikeStep } from "../interfaces.js"; export class PgBooleanFilterStep extends ModifierStep { static $$export = { @@ -22,7 +22,10 @@ export class PgBooleanFilterStep extends ModifierStep { this.alias = $classFilterPlan.alias; } - placeholder($step: ExecutableStep, codec: PgCodec): SQL { + placeholder( + $step: ExecutableStep, + codec: TCodec, + ): SQL { return this.$parent.placeholder($step, codec); } diff --git a/grafast/dataplan-pg/src/filters/pgClassFilter.ts b/grafast/dataplan-pg/src/filters/pgClassFilter.ts index 6db1af2c93..0e9eec8be7 100644 --- a/grafast/dataplan-pg/src/filters/pgClassFilter.ts +++ b/grafast/dataplan-pg/src/filters/pgClassFilter.ts @@ -2,7 +2,7 @@ import type { ExecutableStep } from "grafast"; import { ModifierStep } from "grafast"; import type { SQL } from "pg-sql2"; -import type { PgCodec } from "../interfaces.js"; +import type { AnyPgCodec, PgCodec } from "../interfaces.js"; import type { PgConditionCapableParentStep, PgConditionStep, @@ -30,7 +30,10 @@ export class PgClassFilterStep< this.conditions.push(condition); } - placeholder($step: ExecutableStep, codec: PgCodec): SQL { + placeholder( + $step: ExecutableStep, + codec: TCodec, + ): SQL { return this.$parent.placeholder($step, codec); } diff --git a/grafast/dataplan-pg/src/filters/pgOrFilter.ts b/grafast/dataplan-pg/src/filters/pgOrFilter.ts index 06c930c24f..c7b6f04e71 100644 --- a/grafast/dataplan-pg/src/filters/pgOrFilter.ts +++ b/grafast/dataplan-pg/src/filters/pgOrFilter.ts @@ -3,7 +3,7 @@ import { ModifierStep } from "grafast"; import type { SQL } from "pg-sql2"; import { sql } from "pg-sql2"; -import type { PgCodec, PgConditionLikeStep } from "../interfaces.js"; +import type { AnyPgCodec, PgCodec, PgConditionLikeStep } from "../interfaces.js"; export class PgOrFilterStep extends ModifierStep { static $$export = { @@ -20,7 +20,7 @@ export class PgOrFilterStep extends ModifierStep { this.alias = $classFilterPlan.alias; } - placeholder($step: ExecutableStep, codec: PgCodec): SQL { + placeholder($step: ExecutableStep, codec: TCodec): SQL { return this.$parent.placeholder($step, codec); } diff --git a/grafast/dataplan-pg/src/index.ts b/grafast/dataplan-pg/src/index.ts index e1c7c9b16f..6674d7a18e 100644 --- a/grafast/dataplan-pg/src/index.ts +++ b/grafast/dataplan-pg/src/index.ts @@ -10,7 +10,6 @@ import { ObjectFromPgCodecAttributes, PgCodecAttribute, PgCodecAttributeExtensions, - PgCodecAttributes, PgCodecAttributeVia, PgCodecAttributeViaExplicit, PgEnumCodecSpec, @@ -18,6 +17,12 @@ import { rangeOfCodec, recordCodec, TYPES, + AnyPgCodecAttribute, + DefaultPgCodecAttribute, + DefaultPgCodecAttributeVia, + AnyPgCodecAttributeVia, + AnyPgCodecAttributeViaAttribute, + AnyPgCodecAttributeViaRelationName, } from "./codecs.js"; import { PgBox, @@ -47,6 +52,20 @@ import { PgResourceParameter, PgResourceUnique, PgResourceUniqueExtensions, + AnyPgRegistryBuilder, + AnyPgResource, + AnyPgResourceOptions, + AnyPgResourceParameter, + AnyPgResourceUnique, + AnyScalarPgResource, + DefaultPgResourceOptions, + DefaultPgResource, + DefaultPgResourceParameter, + DefaultPgResourceUnique, + DefaultRegistryBuilder, + EmptyRegistryBuilder, + DefaultPgFunctionResourceOptions, + AnyPgFunctionResourceOptions, } from "./datasource.js"; import { PgClient, @@ -65,7 +84,7 @@ import { PgClassFilterStep } from "./filters/pgClassFilter.js"; import { PgManyFilterStep } from "./filters/pgManyFilter.js"; import { PgOrFilterStep } from "./filters/pgOrFilter.js"; import { - GetPgCodecAttributes, + PgCodecAttributes, GetPgRegistryCodecRelations, GetPgRegistryCodecs, GetPgRegistrySources, @@ -88,7 +107,7 @@ import { PgCodecPolymorphismSingleTypeAttributeSpec, PgCodecPolymorphismSingleTypeSpec, PgCodecPolymorphismUnion, - PgCodecRelation, + PgRelation, PgCodecRelationConfig, PgCodecRelationExtensions, PgCodecWithAttributes, @@ -106,6 +125,43 @@ import { PgTypedExecutableStep, PlanByUniques, TuplePlanMap, + AnyPgCodec, + AnyPgCodecAttributesRecord, + AnyPgCodecRelationConfig, + AnyPgRegistry, + AnyPgRegistryConfig, + AnyScalarPgCodec, + PgCodecAttributesRecord, + PgCodecFromJavaScript, + PgCodecFromPg, + PgCodecFromPostgres, + PgCodecName, + PgCodecRelationBase, + PgCodecRelationConfigLocalCodec, + PgCodecRelationConfigName, + PgCodecRelationConfigRemoteResourceOptions, + PgOrderAttributeSpec, + PgOrderFragmentSpec, + AnyPgRangeItemCodec, + PgRegistryCodecRelations, + PgRegistryCodecs, + PgRegistryConfig, + PgRegistryConfigCodecs, + PgRegistryConfigRelationConfigs, + PgRegistryConfigResourceOptions, + PgRegistryRelationConfigs, + PgRegistryResourceOptions, + PgRegistryResources, + DefaultPgRelation, + AnyPgRelation, + DefaultPgCodec, + DefaultScalarPgCodec, + DefaultPgCodecRelationConfig, + DefaultPgCodecAttributesRecord, + DefaultPgRangeItemCodec, + DefaultPgRegistry, + DefaultPgCodecWithAttributes, + DefaultPgRegistryConfig, } from "./interfaces.js"; import { PgLockableParameter, PgLockCallback } from "./pgLocker.js"; import { @@ -121,7 +177,12 @@ import { pgWhereConditionSpecListToSQL, } from "./steps/pgCondition.js"; import { PgCursorStep } from "./steps/pgCursor.js"; -import { pgDeleteSingle, PgDeleteSingleStep } from "./steps/pgDeleteSingle.js"; +import { + pgDeleteSingle, + PgDeleteSingleStep, + DefaultPgDeleteSingleStep, + AnyPgDeleteSingleStep, +} from "./steps/pgDeleteSingle.js"; import { pgInsertSingle, PgInsertSingleStep } from "./steps/pgInsertSingle.js"; import { pgPageInfo, PgPageInfoStep } from "./steps/pgPageInfo.js"; import { @@ -141,12 +202,17 @@ import { PgSelectParsedCursorStep, PgSelectStep, sqlFromArgDigests, + DefaultPgSelectStep, + AnyPgSelectStep, } from "./steps/pgSelect.js"; import { pgSelectFromRecord, pgSelectSingleFromRecord, PgSelectSinglePlanOptions, PgSelectSingleStep, + AnyPgSelectSinglePlanOptions, + AnyPgSelectSingleStep, + DefaultPgSelectSingleStep, } from "./steps/pgSelectSingle.js"; import { pgSingleTablePolymorphic, @@ -163,7 +229,12 @@ import { PgUnionAllStepMember, PgUnionAllStepOrder, } from "./steps/pgUnionAll.js"; -import { pgUpdateSingle, PgUpdateSingleStep } from "./steps/pgUpdateSingle.js"; +import { + pgUpdateSingle, + PgUpdateSingleStep, + DefaultPgUpdateSingleStep, + AnyPgUpdateSingleStep, +} from "./steps/pgUpdateSingle.js"; import { pgValidateParsedCursor, PgValidateParsedCursorStep, @@ -178,13 +249,72 @@ import { import { assertPgClassSingleStep } from "./utils.js"; export { + DefaultPgResourceOptions, + AnyPgSelectSinglePlanOptions, + DefaultPgCodecAttribute, + DefaultPgCodecAttributeVia, + AnyPgSelectSingleStep, + DefaultPgResource, + DefaultPgResourceParameter, + DefaultPgSelectSingleStep, + DefaultPgRelation, + AnyPgRelation, + DefaultPgCodecWithAttributes, + DefaultPgResourceUnique, + DefaultRegistryBuilder, + EmptyRegistryBuilder, + DefaultPgUpdateSingleStep, + AnyPgUpdateSingleStep, + DefaultPgDeleteSingleStep, + AnyPgDeleteSingleStep, + DefaultPgCodec, + AnyPgRegistryBuilder, + AnyPgResource, + AnyPgResourceOptions, + AnyPgResourceParameter, + AnyPgResourceUnique, + AnyScalarPgResource, + AnyPgCodecAttribute, + AnyPgCodecAttributeVia, + AnyPgCodecAttributeViaAttribute, + AnyPgCodecAttributeViaRelationName, + AnyPgCodec, + AnyPgCodecAttributesRecord, + AnyPgCodecRelationConfig, + AnyPgRegistry, + AnyPgRegistryConfig, + AnyScalarPgCodec, + PgCodecAttributes, + PgCodecAttributesRecord, + PgCodecFromJavaScript, + PgCodecFromPg, + PgCodecFromPostgres, + PgCodecName, + PgCodecRelationBase, + PgCodecRelationConfigLocalCodec, + PgCodecRelationConfigName, + PgCodecRelationConfigRemoteResourceOptions, + PgOrderAttributeSpec, + PgOrderFragmentSpec, + AnyPgRangeItemCodec as PgRangeItemCodec, + PgRegistryCodecRelations, + PgRegistryCodecs, + PgRegistryConfig, + PgRegistryConfigCodecs, + PgRegistryConfigRelationConfigs, + PgRegistryConfigResourceOptions, + PgRegistryRelationConfigs, + PgRegistryResourceOptions, + PgRegistryResources, + PgRelation, assertPgClassSingleStep, digestsFromArgumentSpecs, domainOfCodec, enumCodec, + DefaultPgFunctionResourceOptions, + AnyPgFunctionResourceOptions, getCodecByPgCatalogTypeName, getInnerCodec, - GetPgCodecAttributes, GetPgRegistryCodecRelations, GetPgRegistryCodecs, GetPgRegistrySources, @@ -193,6 +323,12 @@ export { GetPgResourceRegistry, GetPgResourceRelations, GetPgResourceUniques, + DefaultScalarPgCodec, + DefaultPgCodecRelationConfig, + DefaultPgCodecAttributesRecord, + DefaultPgRangeItemCodec, + DefaultPgRegistry, + DefaultPgRegistryConfig, isEnumCodec, KeysOfType, listOfCodec, @@ -215,7 +351,6 @@ export { PgCodecAnyScalar, PgCodecAttribute, PgCodecAttributeExtensions, - PgCodecAttributes, PgCodecAttributeVia, PgCodecAttributeViaExplicit, PgCodecExtensions, @@ -232,7 +367,7 @@ export { PgCodecRefPath, PgCodecRefPathEntry, PgCodecRefs, - PgCodecRelation, + PgRelation as PgCodecRelation, PgCodecRelationConfig, PgCodecRelationExtensions, PgCodecWithAttributes, @@ -250,6 +385,8 @@ export { PgEnumValue, PgExecutor, PgExecutorContext, + AnyPgSelectStep, + DefaultPgSelectStep, PgExecutorContextPlans, PgExecutorInput, PgExecutorMutationOptions, diff --git a/grafast/dataplan-pg/src/interfaces.ts b/grafast/dataplan-pg/src/interfaces.ts index bc100e469e..35ee9ce1d4 100644 --- a/grafast/dataplan-pg/src/interfaces.ts +++ b/grafast/dataplan-pg/src/interfaces.ts @@ -2,12 +2,27 @@ import type { ExecutableStep, GrafastSubscriber, ModifierStep } from "grafast"; import type { SQL, SQLRawValue } from "pg-sql2"; import type { PgAdaptorOptions } from "./adaptors/pg.js"; -import type { PgCodecAttributes } from "./codecs.js"; import type { + AnyPgCodecAttribute, + DefaultPgCodecAttribute, + PgCodecAttribute, + PgCodecAttributeCodec, + PgCodecAttributeName, +} from "./codecs.js"; +import type { + AnyPgResource, + AnyPgResourceOptions, + AnyPgResourceParameter, + AnyPgResourceUnique, + DefaultPgResource, + DefaultPgResourceOptions, PgCodecRefs, PgResource, - PgResourceOptions, - PgResourceParameter, + PgResourceCodec, + PgResourceOptionCodec, + PgResourceOptionName, + PgResourceOptionParameters, + PgResourceOptionUniques, PgResourceUnique, } from "./datasource.js"; import type { PgExecutor, WithPgClient } from "./executor.js"; @@ -20,13 +35,12 @@ 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 = PgResource, -> = - | PgSelectSingleStep - | PgInsertSingleStep - | PgUpdateSingleStep - | PgDeleteSingleStep; +export type PgClassSingleStep = + + | PgSelectSingleStep + | PgInsertSingleStep + | PgUpdateSingleStep + | PgDeleteSingleStep; /** * Given a value of type TInput, returns an `SQL` value to insert into an SQL @@ -116,29 +130,119 @@ export type PgCodecPolymorphism = | PgCodecPolymorphismRelational | PgCodecPolymorphismUnion; +export interface DefaultPgRangeItemCodec + extends PgCodec {} +export interface AnyPgRangeItemCodec + extends PgCodec {} + +export type PgCodecName = U extends PgCodec< + infer TName, + any, + any, + any, + any, + any, + any +> + ? TName + : never; + +export type PgCodecAttributes = U extends PgCodec< + any, + infer TAttributes, + any, + any, + any, + any, + any +> + ? TAttributes[keyof TAttributes] + : never; +export type PgCodecAttributeMap = U extends PgCodec< + any, + infer TAttributes, + any, + any, + any, + any, + any +> + ? TAttributes + : never; +export type PgCodecFromJavaScript = U extends PgCodec< + any, + any, + any, + infer TFromJavaScript, + any, + any, + any +> + ? TFromJavaScript + : never; +export type PgCodecFromPostgres = U extends PgCodec< + any, + any, + infer TFromPostgres, + any, + any, + any, + any +> + ? TFromPostgres + : never; +export type PgCodecFromPg = PgDecode< + PgCodecFromJavaScript, + PgCodecFromPostgres +>; +export interface DefaultPgCodecAttributesRecord + extends PgCodecAttributesRecord {} +export interface AnyPgCodecAttributesRecord + extends PgCodecAttributesRecord {} + +export type PgCodecAttributesRecord< + TCodecAttributes extends AnyPgCodecAttribute, +> = { + [TCodecAttribute in TCodecAttributes as PgCodecAttributeName]: TCodecAttribute; +}; +export interface DefaultPgCodec + extends PgCodec< + string, + PgCodecAttributesRecord, + any, + any, + DefaultPgCodec, + DefaultPgCodec, + DefaultPgCodec + > {} +export interface DefaultScalarPgCodec + extends PgCodec< + string, + never, + any, + any, + DefaultPgCodec, + DefaultPgCodec, + DefaultPgCodec + > {} +export interface AnyScalarPgCodec + extends PgCodec {} +export interface AnyPgCodec + extends PgCodec {} + /** * 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 - | undefined = PgCodec | undefined, - TDomainItemCodec extends - | PgCodec - | undefined = PgCodec | undefined, - TRangeItemCodec extends - | PgCodec - | undefined = - | PgCodec - | undefined, + TName extends string, + TCodecAttributes extends Record, + TFromPostgres, + TFromJavaScript, + TArrayItemCodec extends AnyPgCodec, + TDomainItemCodec extends AnyPgCodec, + TRangeItemCodec extends AnyPgCodec, > { /** * Unique name to identify this codec. @@ -197,7 +301,7 @@ export interface PgCodec< /** * If this is a composite type, the attributes it supports. */ - attributes: TAttributes; + attributes?: TCodecAttributes; /** * A callback to return `'true'` (text string) if the composite type @@ -239,7 +343,7 @@ export interface PgCodec< */ rangeOfCodec?: TRangeItemCodec; - polymorphism?: PgCodecPolymorphism; + polymorphism?: PgCodecPolymorphism; description?: string; @@ -262,20 +366,29 @@ export interface PgCodec< */ executor: PgExecutor | null; } - -export type PgCodecWithAttributes< - TAttributes extends PgCodecAttributes = PgCodecAttributes, -> = PgCodec; - -export type PgCodecAnyScalar = PgCodec< - string, - undefined, +export interface DefaultPgCodecWithAttributes + extends PgCodec< + any, + Record, + any, + any, + never, + any, + never + > {} +export interface PgCodecWithAttributes< + TAttributes extends Record, +> extends PgCodec {} + +export interface PgCodecAnyScalar + extends PgCodec; +> {} export type PgCodecList< TInnerCodec extends PgCodec = PgCodec< @@ -287,7 +400,7 @@ export type PgCodecList< any, any >, -> = PgCodec; +> = PgCodec; export type PgEnumValue = { value: TValue; @@ -300,15 +413,7 @@ export type PgEnumValue = { export interface PgEnumCodec< TName extends string = string, TValue extends string = string, -> extends PgCodec< - TName, - undefined, - string, - TValue, - undefined, - undefined, - undefined - > { +> extends PgCodec { values: PgEnumValue[]; } @@ -316,7 +421,7 @@ export interface PgEnumCodec< * A PgTypedExecutableStep 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 PgTypedExecutableStep +export interface PgTypedExecutableStep extends ExecutableStep { pgCodec: TCodec; } @@ -331,7 +436,7 @@ export type PgOrderFragmentSpec = { /** The expression we're ordering by. */ fragment: SQL; /** The codec of the expression that we're ordering by, this is useful when constructing a cursor for it. */ - codec: PgCodec; + codec: AnyPgCodec; attribute?: never; callback?: never; @@ -345,9 +450,9 @@ export type PgOrderAttributeSpec = { /** An optional expression to wrap this attribute with, and the type that expression returns */ callback?: ( attributeExpression: SQL, - attributeCodec: PgCodec, + attributeCodec: AnyPgCodec, nullable: boolean, - ) => [fragment: SQL, codec: PgCodec, nullable?: boolean]; + ) => [fragment: SQL, codec: AnyPgCodec, nullable?: boolean]; fragment?: never; codec?: never; @@ -369,19 +474,25 @@ export interface PgGroupSpec { } export type TuplePlanMap< - TAttributes extends PgCodecAttributes, - TTuple extends ReadonlyArray, + TAttributes extends AnyPgCodecAttribute, + TTuple extends ReadonlyArray>, > = { [Index in keyof TTuple]: { // Optional attributes - [key in keyof TAttributes as Exclude< - key, - keyof TTuple[number] - >]?: ExecutableStep>; + [attribute in Exclude< + TAttributes, + { name: TTuple[number] } + > as PgCodecAttributeName]?: ExecutableStep< + ReturnType>> + >; } & { // Required unique combination of attributes [key in TTuple[number]]: ExecutableStep< - ReturnType + ReturnType< + PgCodecFromPg< + PgCodecAttributeCodec> + > + > >; }; }; @@ -395,18 +506,13 @@ export type TuplePlanMap< * to. */ export type PlanByUniques< - TAttributes extends PgCodecAttributes, - TUniqueAttributes extends ReadonlyArray>, -> = TAttributes extends PgCodecAttributes - ? TuplePlanMap< - TAttributes, - TUniqueAttributes[number]["attributes"] & string[] - >[number] - : undefined; + TAttributes extends AnyPgCodecAttribute, + TUniqueAttributes extends PgResourceUnique, +> = TuplePlanMap[number]; export type PgConditionLikeStep = (ModifierStep | ExecutableStep) & { alias: SQL; - placeholder($step: ExecutableStep, codec: PgCodec): SQL; + placeholder($step: ExecutableStep, codec: AnyPgCodec): SQL; where(condition: SQL): void; having(condition: SQL): void; }; @@ -523,9 +629,11 @@ export interface MakePgServiceOptions export type PgCodecRelationExtensions = DataplanPg.PgCodecRelationExtensions; export interface PgCodecRelationBase< - TLocalCodec extends PgCodec = PgCodec, + TName extends string = string, + TLocalCodec extends AnyPgCodec = AnyPgCodec, TRemoteAttributes extends string = string, > { + name: TName; /** Where the relationship starts */ localCodec: TLocalCodec; /** If localCodec is polymorphic, which of the concrete subtypes should this relationship apply to? */ @@ -534,7 +642,7 @@ export interface PgCodecRelationBase< /** * The attributes locally used in this relationship. */ - localAttributes: readonly (keyof TLocalCodec["attributes"])[]; + localAttributes: Array>>; /** * The remote attributes that are joined against. @@ -560,91 +668,79 @@ export interface PgCodecRelationBase< description?: string; } - +export interface DefaultPgCodecRelationConfig + extends PgCodecRelationConfig< + string, + DefaultPgCodec, + DefaultPgResourceOptions + > {} +export interface AnyPgCodecRelationConfig + extends PgCodecRelationConfig {} + +export type PgCodecRelationConfigName = U extends PgCodecRelationConfig< + infer TName, + any, + any +> + ? TName + : never; +export type PgCodecRelationConfigLocalCodec = + U extends PgCodecRelationConfig + ? TLocalCodec + : never; +export type PgCodecRelationConfigRemoteResourceOptions = + U extends PgCodecRelationConfig + ? TRemoteResourceOptions + : never; export interface PgCodecRelationConfig< - TLocalCodec extends PgCodec = PgCodecWithAttributes, - TRemoteResourceOptions extends PgResourceOptions = PgResourceOptions< - any, - PgCodecWithAttributes, - any, - any - >, + TName extends string = string, + TLocalCodec extends AnyPgCodec = AnyPgCodec, + TRemoteResourceOptions extends AnyPgResourceOptions = AnyPgResourceOptions, > extends PgCodecRelationBase< + TName, TLocalCodec, - TRemoteResourceOptions extends PgResourceOptions< - any, - PgCodec, - any, - any - > - ? keyof UAttributes - : never + PgCodecAttributeName> > { 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, -> extends PgCodecRelationBase< - TLocalCodec, - TRemoteResource extends PgResource< - any, - PgCodec, - any, - any, - any - > - ? keyof UAttributes - : never - > { - /** - * The remote resource this relation relates to. - */ - remoteResource: TRemoteResource; -} +export interface DefaultPgRegistryConfig + extends PgRegistryConfig< + DefaultPgCodec, + DefaultPgResourceOptions, + DefaultPgCodecRelationConfig + > {} +export interface AnyPgRegistryConfig extends PgRegistryConfig {} +export type PgRegistryConfigCodecs = U extends PgRegistryConfig< + infer TCodecs, + any, + any +> + ? TCodecs + : never; +export type PgRegistryConfigResourceOptions = U extends PgRegistryConfig< + any, + infer TResourceOptions, + any +> + ? TResourceOptions + : never; +export type PgRegistryConfigRelationConfigs = U extends PgRegistryConfig< + any, + any, + infer TRelationConfigs +> + ? TRelationConfigs + : never; 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>>, - readonly PgResourceParameter[] | undefined - >; - }, - TRelations extends { - [codecName in keyof TCodecs]?: { - [relationName in string]: PgCodecRelationConfig< - PgCodec, - PgResourceOptions - >; - }; - }, + TCodecs extends AnyPgCodec, + TResourceOptions extends AnyPgResourceOptions, + TRelationConfigs extends AnyPgCodecRelationConfig, > { - pgCodecs: TCodecs; - pgResources: TResourceOptions; - pgRelations: TRelations; + pgCodecs: PgRegistryCodecs; + pgResources: PgRegistryResourceOptions; + pgRelations: PgRegistryRelationConfigs; } // https://github.com/microsoft/TypeScript/issues/47980#issuecomment-1049304607 @@ -652,150 +748,159 @@ export type Expand = 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< +export type PgRegistryCodecs = { + [TCodec in TCodecs as PgCodecName]: TCodec; +}; + +export type PgRegistryResourceOptions< + TResourceOptions extends AnyPgResourceOptions, +> = { + [TResourceOption in TResourceOptions as PgResourceOptionName]: TResourceOption; +}; + +export type PgRegistryResources< + TResourceOptions extends AnyPgResourceOptions, + TRegistry extends AnyPgRegistry, +> = { + [TResourceOption in TResourceOptions as PgResourceOptionName]: PgResource< + PgResourceOptionName, + PgResourceOptionCodec, + PgResourceOptionUniques, + PgResourceOptionParameters, + TRegistry + >; +}; +export type PgRegistryRelationConfigs< + TRelationConfigs extends AnyPgCodecRelationConfig, +> = { + [TRelationConfig in TRelationConfigs as PgCodecName< + PgCodecRelationConfigLocalCodec + >]: Record, TRelationConfig>; +}; + +export interface AnyPgRelation extends PgRelation {} +export interface DefaultPgRelation + extends PgRelation< string, - PgCodec - >, - TResourceOptions extends { - [name in string]: PgResourceOptions< - name, - PgCodec, // TCodecs[keyof TCodecs], - ReadonlyArray>, - readonly PgResourceParameter[] | undefined + DefaultPgCodec, + DefaultPgResourceOptions, + DefaultPgRegistry + > {} + +export interface PgRelation< + TName extends string, + TLocalCodec extends AnyPgCodec, + TRemoteResourceOptions extends AnyPgResourceOptions, + TRegistry extends AnyPgRegistry, +> extends Omit< + PgCodecRelationConfig, + "remoteResourceOptions" + > { + remoteResource: PgResource< + PgResourceOptionName, + PgResourceOptionCodec, + PgResourceOptionUniques, + PgResourceOptionParameters, + TRegistry + >; +} + +export type PgRegistryCodecRelations< + TRelationConfigs extends AnyPgCodecRelationConfig, + TRegistry extends AnyPgRegistry, +> = { + [TRelationConfig in TRelationConfigs as PgCodecName< + PgCodecRelationConfigLocalCodec + >]: { + [TRelationName in PgCodecRelationConfigName]: PgRelation< + PgCodecRelationConfigName< + Extract + >, + PgCodecRelationConfigLocalCodec< + Extract + >, + PgCodecRelationConfigRemoteResourceOptions< + Extract + >, + TRegistry >; - } = Record< - string, - PgResourceOptions< - string, - // TYPES: This maybe shouldn't be PgCodecWithAttributes, but PgCodec instead? - PgCodecWithAttributes, // TCodecs[keyof TCodecs], - ReadonlyArray>, - readonly PgResourceParameter[] | undefined - > - >, - TRelations extends { - [codecName in keyof TCodecs]?: { - [relationName in string]: PgCodecRelationConfig< - // TCodecs[keyof TCodecs] & - PgCodec, - // TResourceOptions[keyof TResourceOptions] & - PgResourceOptions< - any, - // TCodecs[keyof TCodecs] & - PgCodecWithAttributes, - any, - any - > - >; - }; - } = Record< - string, - Record< - string, - PgCodecRelationConfig< - // TCodecs[keyof TCodecs] & - PgCodec, - // TResourceOptions[keyof TResourceOptions] & - PgResourceOptions< - any, - // TCodecs[keyof TCodecs] & - PgCodecWithAttributes, - any, - any - > - > - > - >, -> { - 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 - > - : never; - }; - pgRelations: { - [codecName in keyof TRelations]: { - [relationName in keyof TRelations[codecName]]: Expand< - Omit & { - remoteResource: TRelations[codecName][relationName] extends { - remoteResourceOptions: PgResourceOptions< - infer UName, - infer UCodec, - infer UUniques, - infer UParameters - >; - } - ? PgResource< - UName, - UCodec, - UUniques, - UParameters, - PgRegistry - > - : never; - } - >; - }; }; +}; +export interface AnyPgRegistry extends PgRegistry {} +export interface DefaultPgRegistry + extends PgRegistry< + DefaultPgCodec, + DefaultPgResourceOptions, + DefaultPgCodecRelationConfig + > {} +export interface EmptyPgRegistry extends PgRegistry {} + +export interface PgRegistry< + TCodecs extends AnyPgCodec, + TResourceOptions extends AnyPgResourceOptions, + TRelationConfigs extends AnyPgCodecRelationConfig, +> { + pgCodecs: PgRegistryCodecs; + pgResources: PgRegistryResources; + pgRelations: PgRegistryCodecRelations; } -export type GetPgRegistryCodecs> = - TRegistry["pgCodecs"]; +export type GetPgRegistryCodecs = U extends PgRegistry< + infer TCodecs, + any, + any +> + ? TCodecs + : never; -export type GetPgRegistrySources> = +export type GetPgRegistryRelations = U extends PgRegistry< + any, + any, + infer TRelationConfigs +> + ? PgRegistryCodecRelations + : never; + +export type GetPgRegistrySources = TRegistry["pgResources"]; +export type GetPgRegistryCodecRelationConfigs< + TRegistry extends AnyPgRegistry, + TCodec extends AnyPgCodec, +> = TRegistry extends PgRegistry + ? Extract } }> + : never; + export type GetPgRegistryCodecRelations< - TRegistry extends PgRegistry, - TCodec extends PgCodec, -> = TRegistry["pgRelations"][TCodec["name"]]; - -export type GetPgCodecAttributes< - TCodec extends PgCodec, -> = TCodec extends PgCodec - ? UAttributes extends undefined - ? never - : UAttributes - : PgCodecAttributes; - -export type GetPgResourceRegistry< - TResource extends PgResource, -> = TResource["registry"]; - -export type GetPgResourceCodec< - TResource extends PgResource, -> = TResource["codec"]; - -export type GetPgResourceAttributes< - TResource extends PgResource, -> = GetPgCodecAttributes; - -export type GetPgResourceRelations< - TResource extends PgResource, -> = TResource["registry"]["pgRelations"][TResource["codec"]["name"]]; - -export type GetPgResourceUniques< - TResource extends PgResource, -> = TResource["uniques"]; + TRegistry extends AnyPgRegistry, + TCodec extends AnyPgCodec, +> = GetPgRegistryRelations[PgCodecName]; + +export type GetPgResourceRegistry = + TResource["registry"]; + +export type GetPgResourceCodec = + TResource["codec"]; + +export type GetPgResourceAttributes = + PgCodecAttributes>; +export type GetPgResourceAttributeMap = + PgCodecAttributeMap>; +export type GetPgResourceRelationConfigs = + GetPgRegistryCodecRelationConfigs; + +export type GetPgResourceRelations = + GetPgRegistryCodecRelations< + GetPgResourceRegistry, + PgResourceCodec + >; + +export type GetPgResourceUniques = U extends PgResource< + any, + any, + infer TUniques, + any, + any +> + ? TUniques + : never; diff --git a/grafast/dataplan-pg/src/pgLocker.ts b/grafast/dataplan-pg/src/pgLocker.ts index 0c6e4f4c73..d7e0f8a4c1 100644 --- a/grafast/dataplan-pg/src/pgLocker.ts +++ b/grafast/dataplan-pg/src/pgLocker.ts @@ -1,6 +1,7 @@ import { isDev } from "grafast"; -import type { PgSelectStep, PgUnionAllStep } from "./index"; +import { AnyPgSelectStep } from "./steps/pgSelect"; +import { AnyPgUnionAllStep } from "./steps/pgUnionAll"; export type PgLockableParameter = | "orderBy" @@ -8,13 +9,13 @@ export type PgLockableParameter = | "last" | "offset" | "groupBy"; -export type PgLockCallback< - TStep extends PgSelectStep | PgUnionAllStep, -> = (step: TStep) => void; +export type PgLockCallback = (step: TStep) => void; -export class PgLocker< - TStep extends PgSelectStep | PgUnionAllStep, -> { +export type AnyPgStep = AnyPgSelectStep | AnyPgUnionAllStep; + +export interface AnyPgLocker extends PgLocker {} + +export class PgLocker { /** * Determines if the PgSelectStep is "locked" - i.e. its * FROM,JOINs,WHERE,ORDER BY,LIMIT,OFFSET cannot be changed. Note this does diff --git a/grafast/dataplan-pg/src/steps/pgClassExpression.ts b/grafast/dataplan-pg/src/steps/pgClassExpression.ts index 9ace5a877f..d522327b47 100644 --- a/grafast/dataplan-pg/src/steps/pgClassExpression.ts +++ b/grafast/dataplan-pg/src/steps/pgClassExpression.ts @@ -3,11 +3,11 @@ import { access, exportAs, UnbatchedExecutableStep } from "grafast"; import type { SQL } from "pg-sql2"; import sql from "pg-sql2"; -import type { PgResource } from "../datasource.js"; +import type { AnyPgResource, PgResource } from "../datasource.js"; import type { - GetPgCodecAttributes, + AnyPgCodec, PgClassSingleStep, - PgCodec, + PgCodecAttributes, PgTypedExecutableStep, } from "../interfaces.js"; import { PgDeleteSingleStep } from "./pgDeleteSingle.js"; @@ -15,6 +15,7 @@ import { PgInsertSingleStep } from "./pgInsertSingle.js"; import { PgSelectSingleStep } from "./pgSelectSingle.js"; import { PgUnionAllSingleStep } from "./pgUnionAll.js"; import { PgUpdateSingleStep } from "./pgUpdateSingle.js"; +import { PgCodecAttributeCodec, PgCodecAttributeName } from "../codecs.js"; // const debugPlan = debugFactory("@dataplan/pg:PgClassExpressionStep:plan"); // const debugExecute = debugFactory( "@dataplan/pg:PgClassExpressionStep:execute",); @@ -28,8 +29,8 @@ import { PgUpdateSingleStep } from "./pgUpdateSingle.js"; * of another layer of plan. */ export class PgClassExpressionStep< - TExpressionCodec extends PgCodec, - TResource extends PgResource, + TExpressionCodec extends AnyPgCodec, + TResource extends AnyPgResource, > extends UnbatchedExecutableStep implements PgTypedExecutableStep @@ -160,10 +161,14 @@ export class PgClassExpressionStep< Instead, we'll lie and ignore the `AccessStep` case */ - public get>( + public get< + TAttr extends PgCodecAttributeName>, + >( attributeName: TAttr, ): PgClassExpressionStep< - GetPgCodecAttributes[TAttr]["codec"], + PgCodecAttributeCodec< + Extract, { name: TAttr }> + >, TResource > { const attributes = this.pgCodec.attributes; @@ -171,7 +176,7 @@ export class PgClassExpressionStep< // Fall back to access, since this could be a 'point' or similar type that doesn't have attributes in Postgres but does in JS. return access(this, attributeName) as any; } - const attribute = attributes[attributeName as string]; + const attribute = attributes[attributeName]; if (!attribute) { throw new Error( `Cannot call ${this}.get('${String( @@ -197,8 +202,8 @@ export class PgClassExpressionStep< } const sqlExpr = pgClassExpression(this.getParentStep(), attribute.codec); return sqlExpr`${sql.parens(this.expression, true)}.${sql.identifier( - attributeName as string, - )}` as any; + attributeName, + )}`; } public getParentStep(): PgClassSingleStep | PgUnionAllSingleStep { @@ -265,8 +270,8 @@ export class PgClassExpressionStep< * that will be selected. */ function pgClassExpression< - TExpressionCodec extends PgCodec, - TResource extends PgResource, + TExpressionCodec extends AnyPgCodec, + TResource extends AnyPgResource, >( table: PgClassSingleStep | PgUnionAllSingleStep, codec: TExpressionCodec, diff --git a/grafast/dataplan-pg/src/steps/pgCondition.ts b/grafast/dataplan-pg/src/steps/pgCondition.ts index 1f4d656d03..76a5d647bd 100644 --- a/grafast/dataplan-pg/src/steps/pgCondition.ts +++ b/grafast/dataplan-pg/src/steps/pgCondition.ts @@ -4,7 +4,7 @@ import type { SQL } from "pg-sql2"; import { sql } from "pg-sql2"; import { TYPES } from "../index.js"; -import type { PgCodec } from "../interfaces.js"; +import type { AnyPgCodec, PgCodec } from "../interfaces.js"; export type PgWhereConditionSpec = | SQL @@ -22,7 +22,10 @@ export type PgConditionStepExtensions = DataplanPg.PgConditionStepExtensions; export interface PgConditionCapableParentStep extends BaseStep { alias: SQL; - placeholder($step: ExecutableStep, codec: PgCodec): SQL; + placeholder( + $step: ExecutableStep, + codec: TCodec, + ): SQL; where(condition: PgWhereConditionSpec): void; having?(condition: PgHavingConditionSpec): void; } @@ -135,7 +138,10 @@ export class PgConditionStep< this.havingConditions.push(condition); } - placeholder($step: ExecutableStep, codec: PgCodec): SQL { + placeholder( + $step: ExecutableStep, + codec: TCodec, + ): SQL { return this.$parent.placeholder($step, codec); } diff --git a/grafast/dataplan-pg/src/steps/pgDeleteSingle.ts b/grafast/dataplan-pg/src/steps/pgDeleteSingle.ts index 6792b584de..7a3b07b1bd 100644 --- a/grafast/dataplan-pg/src/steps/pgDeleteSingle.ts +++ b/grafast/dataplan-pg/src/steps/pgDeleteSingle.ts @@ -7,18 +7,22 @@ import { ExecutableStep, exportAs, isDev, SafeError } from "grafast"; import type { SQL, SQLRawValue } from "pg-sql2"; import sql from "pg-sql2"; -import type { PgCodecAttribute } from "../codecs.js"; -import type { PgResource, PgResourceUnique } from "../index.js"; import { inspect } from "../inspect.js"; import type { + AnyPgCodec, GetPgResourceAttributes, GetPgResourceCodec, GetPgResourceUniques, - PgCodec, PlanByUniques, } from "../interfaces.js"; import type { PgClassExpressionStep } from "./pgClassExpression.js"; import { pgClassExpression } from "./pgClassExpression.js"; +import { + AnyPgResource, + DefaultPgResource, + PgResourceUnique, +} from "../datasource.js"; +import { PgCodecAttributeCodec, PgCodecAttributeName } from "../codecs.js"; type QueryValueDetailsBySymbol = Map< symbol, @@ -36,11 +40,14 @@ interface PgDeletePlanFinalizeResults { queryValueDetailsBySymbol: QueryValueDetailsBySymbol; } +export interface AnyPgDeleteSingleStep extends PgDeleteSingleStep {} +export interface DefaultPgDeleteSingleStep + extends PgDeleteSingleStep {} /** * Deletes a row in the database, can return columns from the deleted row. */ export class PgDeleteSingleStep< - TResource extends PgResource = PgResource, + TResource extends AnyPgResource, > extends ExecutableStep { static $$export = { moduleName: "@dataplan/pg", @@ -75,9 +82,9 @@ export class PgDeleteSingleStep< * The attributes and their dependency ids for us to find the record by. */ private getBys: Array<{ - name: keyof GetPgResourceAttributes; + name: PgCodecAttributeName>; depId: number; - pgCodec: PgCodec; + pgCodec: AnyPgCodec; }> = []; /** @@ -117,14 +124,18 @@ export class PgDeleteSingleStep< this.alias = sql.identifier(this.symbol); this.contextId = this.addDependency(this.resource.executor.context()); - const keys: ReadonlyArray> = getBy - ? (Object.keys(getBy) as Array>) + const keys = getBy + ? (Object.keys(getBy) as Array< + PgCodecAttributeName> + >) : []; if ( - !(this.resource.uniques as PgResourceUnique[]).some((uniq) => - uniq.attributes.every((key) => keys.includes(key as any)), - ) + !( + this.resource.uniques as Array< + PgResourceUnique> + > + ).some((uniq) => uniq.attributes.every((key) => keys.includes(key))) ) { throw new Error( `Attempted to build 'PgDeleteSingleStep' with a non-unique getBy keys ('${keys.join( @@ -145,13 +156,13 @@ export class PgDeleteSingleStep< ); } } - const value = (getBy as any)![name as any]; + const value = getBy[name]; const depId = this.addDependency(value); - const attribute = ( - this.resource.codec.attributes as GetPgResourceAttributes - )[name]; - const pgCodec = attribute.codec; - this.getBys.push({ name, depId, pgCodec }); + const attribute = this.resource.codec.attributes?.[name]; + if (attribute) { + const pgCodec = attribute.codec; + this.getBys.push({ name, depId, pgCodec }); + } }); } @@ -163,14 +174,15 @@ export class PgDeleteSingleStep< * Returns a plan representing a named attribute (e.g. column) from the newly * deleteed row. */ - get>( + get>>( attr: TAttr, ): PgClassExpressionStep< - GetPgResourceAttributes[TAttr]["codec"], + PgCodecAttributeCodec< + Extract, { name: TAttr }> + >, TResource > { - const resourceAttribute: PgCodecAttribute = - this.resource.codec.attributes![attr as string]; + const resourceAttribute = this.resource.codec.attributes![attr]; if (!resourceAttribute) { throw new Error( `${this.resource} does not define an attribute named '${String(attr)}'`, @@ -381,9 +393,7 @@ export class PgDeleteSingleStep< /** * Delete a row in `resource` identified by the `getBy` unique condition. */ -export function pgDeleteSingle< - TResource extends PgResource, ->( +export function pgDeleteSingle( resource: TResource, getBy: PlanByUniques< GetPgResourceAttributes, diff --git a/grafast/dataplan-pg/src/steps/pgInsertSingle.ts b/grafast/dataplan-pg/src/steps/pgInsertSingle.ts index 3881d6d468..cefe882c71 100644 --- a/grafast/dataplan-pg/src/steps/pgInsertSingle.ts +++ b/grafast/dataplan-pg/src/steps/pgInsertSingle.ts @@ -9,17 +9,22 @@ import { ExecutableStep, exportAs, isDev, setter } from "grafast"; import type { SQL, SQLRawValue } from "pg-sql2"; import sql from "pg-sql2"; -import type { PgCodecAttribute } from "../codecs.js"; -import type { PgResource } from "../index.js"; +import type { + AnyPgCodecAttribute, + PgCodecAttribute, + PgCodecAttributeCodec, + PgCodecAttributeName, +} from "../codecs.js"; import { inspect } from "../inspect.js"; import type { + AnyPgCodec, GetPgResourceAttributes, GetPgResourceCodec, - PgCodec, PgTypedExecutableStep, } from "../interfaces.js"; import type { PgClassExpressionStep } from "./pgClassExpression.js"; import { pgClassExpression } from "./pgClassExpression.js"; +import { AnyPgResource } from "../datasource.js"; const EMPTY_MAP = new Map(); @@ -42,16 +47,13 @@ interface PgInsertSinglePlanFinalizeResults { /** * Inserts a row into resource with the given specified attribute values. */ -export class PgInsertSingleStep< - TResource extends PgResource = PgResource, - > +export class PgInsertSingleStep extends ExecutableStep< unknown[] // tuple depending on what's selected > implements SetterCapableStep<{ - [key in keyof GetPgResourceAttributes & - string]: ExecutableStep; + [attribute in GetPgResourceAttributes as PgCodecAttributeName]: ExecutableStep; }> { static $$export = { @@ -87,10 +89,10 @@ export class PgInsertSingleStep< * The attributes and their dependency ids for us to insert. */ private attributes: Array<{ - name: keyof GetPgResourceAttributes; + name: PgCodecAttributeName>; depId: number; // This isn't really needed, we can look it up in the codec, but it acts as a quick reference. - pgCodec: PgCodec; + pgCodec: AnyPgCodec; }> = []; /** @@ -117,9 +119,14 @@ export class PgInsertSingleStep< constructor( resource: TResource, attributes?: { - [key in keyof GetPgResourceAttributes]?: + [attribute in GetPgResourceAttributes as PgCodecAttributeName]?: | PgTypedExecutableStep< - GetPgResourceAttributes[key]["codec"] + PgCodecAttributeCodec< + Extract< + GetPgResourceAttributes, + { name: PgCodecAttributeName } + > + > > | ExecutableStep; }, @@ -131,12 +138,16 @@ export class PgInsertSingleStep< this.alias = sql.identifier(this.symbol); this.contextId = this.addDependency(this.resource.executor.context()); if (attributes) { - Object.entries(attributes).forEach(([key, value]) => { + ( + Object.entries(attributes) as Array< + [ + PgCodecAttributeName>, + ExecutableStep, + ] + > + ).forEach(([key, value]) => { if (value) { - this.set( - key as keyof GetPgResourceAttributes, - value as ExecutableStep, - ); + this.set(key, value); } }); } @@ -146,7 +157,7 @@ export class PgInsertSingleStep< return `${this.resource.name}(${this.attributes.map((a) => a.name)})`; } - set>( + set>>( name: TKey, value: ExecutableStep, // | PgTypedExecutableStep ): void { @@ -160,9 +171,7 @@ export class PgInsertSingleStep< ); } } - const attribute = ( - this.resource.codec.attributes as GetPgResourceAttributes - )?.[name]; + const attribute = this.resource.codec.attributes?.[name]; if (!attribute) { throw new Error( `Attribute ${String(name)} not found in ${this.resource.codec}`, @@ -175,8 +184,7 @@ export class PgInsertSingleStep< setPlan(): SetterStep< { - [key in keyof GetPgResourceAttributes & - string]: ExecutableStep; + [attribute in GetPgResourceAttributes as PgCodecAttributeName]: ExecutableStep; }, this > { @@ -192,12 +200,13 @@ export class PgInsertSingleStep< * Returns a plan representing a named attribute (e.g. column) from the newly * inserted row. */ - get>( + get>>( attr: TAttr, ): PgClassExpressionStep< - GetPgResourceAttributes[TAttr] extends PgCodecAttribute< - infer UCodec - > + Extract< + GetPgResourceAttributes, + { name: TAttr } + > extends PgCodecAttribute ? UCodec : never, TResource @@ -205,7 +214,7 @@ export class PgInsertSingleStep< if (!this.resource.codec.attributes) { throw new Error(`Cannot call .get() when there's no attributes.`); } - const resourceAttribute: PgCodecAttribute = + const resourceAttribute: AnyPgCodecAttribute = this.resource.codec.attributes[attr as string]; if (!resourceAttribute) { throw new Error( @@ -411,13 +420,18 @@ export class PgInsertSingleStep< /** * Inserts a row into resource with the given specified attribute values. */ -export function pgInsertSingle< - TResource extends PgResource, ->( +export function pgInsertSingle( resource: TResource, attributes?: { - [key in keyof GetPgResourceAttributes]?: - | PgTypedExecutableStep[key]["codec"]> + [attribute in GetPgResourceAttributes as PgCodecAttributeName]?: + | PgTypedExecutableStep< + PgCodecAttributeCodec< + Extract< + GetPgResourceAttributes, + { name: PgCodecAttributeName } + > + > + > | ExecutableStep; }, ): PgInsertSingleStep { diff --git a/grafast/dataplan-pg/src/steps/pgSelect.ts b/grafast/dataplan-pg/src/steps/pgSelect.ts index 04bdca06fc..66ec8bd017 100644 --- a/grafast/dataplan-pg/src/steps/pgSelect.ts +++ b/grafast/dataplan-pg/src/steps/pgSelect.ts @@ -42,15 +42,22 @@ import { import type { SQL, SQLRawValue } from "pg-sql2"; import sql, { $$symbolToIdentifier, arraysMatch } from "pg-sql2"; -import type { PgCodecAttributes } from "../codecs.js"; import { listOfCodec, TYPES } from "../codecs.js"; -import type { PgResource, PgResourceUnique } from "../datasource.js"; import type { + AnyPgResource, + AnyPgResourceUnique, + AnyScalarPgResource, + DefaultPgResource, + PgResource, + PgResourceCodec, +} from "../datasource.js"; +import type { + AnyPgCodec, GetPgResourceAttributes, GetPgResourceCodec, + GetPgResourceRelationConfigs, GetPgResourceRelations, PgCodec, - PgCodecRelation, PgGroupSpec, PgOrderSpec, PgTypedExecutableStep, @@ -157,26 +164,26 @@ type PgSelectPlanJoin = */ type PgSelectPlaceholder = { dependencyIndex: number; - codec: PgCodec; + codec: AnyPgCodec; symbol: symbol; }; export type PgSelectIdentifierSpec = | { step: ExecutableStep; - codec: PgCodec; + codec: AnyPgCodec; matches: (alias: SQL) => SQL; } | { step: PgTypedExecutableStep; - codec?: PgCodec; + codec?: AnyPgCodec; matches: (alias: SQL) => SQL; }; export type PgSelectArgumentSpec = | { step: ExecutableStep; - pgCodec: PgCodec; + pgCodec: AnyPgCodec; name?: string; } | { @@ -192,7 +199,7 @@ export interface PgSelectArgumentDigest { interface QueryValue { dependencyIndex: number; - codec: PgCodec; + codec: AnyPgCodec; } function assertSensible(step: ExecutableStep): void { @@ -210,9 +217,7 @@ function assertSensible(step: ExecutableStep): void { export type PgSelectMode = "normal" | "aggregate" | "mutation"; -export interface PgSelectOptions< - TResource extends PgResource = PgResource, -> { +export interface PgSelectOptions { /** * Tells us what we're dealing with - data type, columns, where to get it * from, what it's called, etc. Many of these details can be overridden @@ -260,6 +265,9 @@ export interface PgSelectOptions< joinAsLateral?: boolean; } +export interface DefaultPgSelectStep extends PgSelectStep {} +export interface AnyPgSelectStep extends PgSelectStep {} + /** * This represents selecting from a class-like entity (table, view, etc); i.e. * it represents `SELECT , FROM `. You can also add @@ -269,9 +277,7 @@ export interface PgSelectOptions< * don't allow `UNION`/`INTERSECT`/`EXCEPT`/`FOR UPDATE`/etc at this time, * purely because it hasn't been sufficiently considered. */ -export class PgSelectStep< - TResource extends PgResource = PgResource, - > +export class PgSelectStep extends ExecutableStep< ReadonlyArray > @@ -322,7 +328,10 @@ export class PgSelectStep< // JOIN - private relationJoins: Map, SQL>; + private relationJoins: Map< + GetPgResourceRelationConfigs["name"], + SQL + >; private joins: Array; // WHERE @@ -483,8 +492,9 @@ export class PgSelectStep< // -------------------- public readonly mode: PgSelectMode; - - private locker: PgLocker = new PgLocker(this); + // DO NOT REMOVE THE `any` here. It'll give massive hard to track type errors if you do. + // I suspect it's due to circular type inference issues. + private locker = new PgLocker(this); constructor(options: PgSelectOptions); constructor(cloneFrom: PgSelectStep, mode?: PgSelectMode); @@ -763,11 +773,11 @@ export class PgSelectStep< return this.isUnique; } - public placeholder($step: PgTypedExecutableStep): SQL; - public placeholder($step: ExecutableStep, codec: PgCodec): SQL; + public placeholder($step: PgTypedExecutableStep): SQL; + public placeholder($step: ExecutableStep, codec: AnyPgCodec): SQL; public placeholder( - $step: ExecutableStep | PgTypedExecutableStep, - overrideCodec?: PgCodec, + $step: ExecutableStep | PgTypedExecutableStep, + overrideCodec?: AnyPgCodec, ): SQL { if (this.locker.locked) { throw new Error(`${this}: cannot add placeholders once plan is locked`); @@ -809,9 +819,7 @@ export class PgSelectStep< public singleRelation< TRelationName extends keyof GetPgResourceRelations & string, >(relationIdentifier: TRelationName): SQL { - const relation = this.resource.getRelation( - relationIdentifier, - ) as PgCodecRelation; + const relation = this.resource.getRelation(relationIdentifier); if (!relation) { throw new Error( `${this.resource} does not have a relation named '${String( @@ -2196,7 +2204,7 @@ ${lateralText};`; if ($p === this) { return true; } - const p = $p as PgSelectStep; + const p = $p as PgSelectStep; // If SELECT, FROM, JOIN, WHERE, ORDER, GROUP BY, HAVING, LIMIT, OFFSET // all match with one of our peers then we can replace ourself with one // of our peers. NOTE: we do _not_ merge SELECTs at this stage because @@ -2376,7 +2384,7 @@ ${lateralText};`; } } - private mergeSelectsWith>( + private mergeSelectsWith>( otherPlan: TOtherStep, ): { [desiredIndex: string]: string; @@ -2400,7 +2408,7 @@ ${lateralText};`; return actualKeyByDesiredKey; } - private mergePlaceholdersInto>( + private mergePlaceholdersInto>( otherPlan: TOtherStep, ): void { for (const placeholder of this.placeholders) { @@ -2479,7 +2487,7 @@ ${lateralText};`; !this.joins.some((j) => j.type !== "left") ) { // Inline ourself into our parent if we can. - let t: PgSelectStep | null | undefined = undefined; + let t: PgSelectStep | null | undefined = undefined; let p: ExecutableStep | undefined = undefined; for ( let dependencyIndex = 0, l = this.dependencies.length; @@ -2782,9 +2790,9 @@ ${lateralText};`; * Beware: if you call this and the database might actually return more than * one record then you're potentially in for a Bad Time. */ - singleAsRecord( - options?: PgSelectSinglePlanOptions, - ): PgSelectSingleStep { + singleAsRecord< + TSelectSinglePlanOptions extends PgSelectSinglePlanOptions, + >(options?: TSelectSinglePlanOptions): PgSelectSingleStep { this.setUnique(true); return new PgSelectSingleStep(this, first(this), options); } @@ -2798,28 +2806,26 @@ ${lateralText};`; * Beware: if you call this and the database might actually return more than * one record then you're potentially in for a Bad Time. */ - single( - options?: PgSelectSinglePlanOptions, - ): TResource extends PgResource< - any, - PgCodec, - any, - any, - any - > - ? UAttributes extends PgCodecAttributes - ? PgSelectSingleStep - : PgClassExpressionStep< - PgCodec, - TResource - > - : never { + single>( + options?: TSelectSinglePlanOptions, + ): [GetPgResourceAttributes] extends [never] + ? PgClassExpressionStep, TResource> + : PgSelectSingleStep; + single>( + options?: TSelectSinglePlanOptions, + ): + | PgClassExpressionStep, TResource> + | PgSelectSingleStep { const $single = this.singleAsRecord(options); const isScalar = !this.resource.codec.attributes; - return (isScalar ? $single.getSelfNamed() : $single) as any; + + return isScalar ? $single.getSelfNamed() : $single; } - row($row: ExecutableStep, options?: PgSelectSinglePlanOptions) { + row>( + $row: ExecutableStep, + options?: TSelectSinglePlanOptions, + ) { return new PgSelectSingleStep(this, $row, options); } @@ -2838,23 +2844,18 @@ ${lateralText};`; */ listItem( itemPlan: ExecutableStep, - ): TResource extends PgResource< - any, - PgCodec, - any, - any, - any - > - ? UAttributes extends PgCodecAttributes - ? PgSelectSingleStep - : PgClassExpressionStep< - PgCodec, - TResource - > - : never { + ): TResource extends AnyScalarPgResource + ? PgClassExpressionStep, TResource> + : PgSelectSingleStep; + listItem( + itemPlan: ExecutableStep, + ): + | PgClassExpressionStep, TResource> + | PgSelectSingleStep { const $single = new PgSelectSingleStep(this, itemPlan); const isScalar = !this.resource.codec.attributes; - return (isScalar ? $single.getSelfNamed() : $single) as any; + + return isScalar ? $single.getSelfNamed() : $single; } // -------------------- @@ -2896,16 +2897,18 @@ function joinMatches( /** * Apply a default order in case our default is not unique. */ -function ensureOrderIsUnique(step: PgSelectStep) { +function ensureOrderIsUnique< + TResource extends PgResource, +>(step: PgSelectStep) { // No need to order a unique record if (step.unique()) return; - const unique = (step.resource.uniques as PgResourceUnique[])[0]; + const unique = step.resource.uniques[0]; if (unique !== undefined) { const ordersIsUnique = step.orderIsUnique(); if (!ordersIsUnique) { unique.attributes.forEach((c) => { step.orderBy({ - fragment: sql`${step.alias}.${sql.identifier(c as string)}`, + fragment: sql`${step.alias}.${sql.identifier(c)}`, codec: step.resource.codec.attributes![c].codec, direction: "ASC", }); @@ -2915,7 +2918,7 @@ function ensureOrderIsUnique(step: PgSelectStep) { } } -export function pgSelect>( +export function pgSelect( options: PgSelectOptions, ): PgSelectStep { return new PgSelectStep(options); @@ -2925,21 +2928,11 @@ exportAs("@dataplan/pg", pgSelect, "pgSelect"); /** * Turns a list of records (e.g. from PgSelectSingleStep.record()) back into a PgSelect. */ -export function pgSelectFromRecords< - TResource extends PgResource, ->( +export function pgSelectFromRecords( resource: TResource, records: | PgClassExpressionStep< - PgCodec< - any, - undefined, - any, - any, - GetPgResourceCodec, - any, - any - >, + PgCodec, any, any>, TResource > | ExecutableStep, @@ -2949,7 +2942,7 @@ export function pgSelectFromRecords< identifiers: [], from: (records) => sql`unnest(${records.placeholder})`, args: [{ step: records, pgCodec: listOfCodec(resource.codec) }], - }) as PgSelectStep; + }); } exportAs("@dataplan/pg", pgSelectFromRecords, "pgSelectFromRecords"); @@ -2972,7 +2965,7 @@ exportAs("@dataplan/pg", sqlFromArgDigests, "sqlFromArgDigests"); export function digestsFromArgumentSpecs( $placeholderable: { - placeholder(step: ExecutableStep, codec: PgCodec): SQL; + placeholder(step: ExecutableStep, codec: AnyPgCodec): SQL; }, specs: PgSelectArgumentSpec[], digests: PgSelectArgumentDigest[] = [], @@ -3010,8 +3003,8 @@ exportAs("@dataplan/pg", digestsFromArgumentSpecs, "digestsFromArgumentSpecs"); export function getFragmentAndCodecFromOrder( alias: SQL, order: PgOrderSpec, - codecOrCodecs: PgCodec | PgCodec[], -): [fragment: SQL, codec: PgCodec, isNullable?: boolean] { + codecOrCodecs: AnyPgCodec | AnyPgCodec[], +): [fragment: SQL, codec: AnyPgCodec, isNullable?: boolean] { if (order.attribute != null) { const colFrag = sql`${alias}.${sql.identifier(order.attribute)}`; const isArray = Array.isArray(codecOrCodecs); diff --git a/grafast/dataplan-pg/src/steps/pgSelectSingle.ts b/grafast/dataplan-pg/src/steps/pgSelectSingle.ts index 6e2f4b454a..61c4912138 100644 --- a/grafast/dataplan-pg/src/steps/pgSelectSingle.ts +++ b/grafast/dataplan-pg/src/steps/pgSelectSingle.ts @@ -10,17 +10,31 @@ import type { SQL } from "pg-sql2"; import sql from "pg-sql2"; import type { + AnyPgCodecAttribute, ObjectFromPgCodecAttributes, - PgCodecAttribute, + PgCodecAttributeCodec, + PgCodecAttributeName, } from "../codecs.js"; import { TYPES } from "../codecs.js"; -import type { PgResource } from "../datasource.js"; import type { + AnyPgResource, + DefaultPgResource, + PgResource, + PgResourceCodec, + PgResourceRegistry, +} from "../datasource.js"; +import type { + AnyPgCodec, + GetPgRegistryCodecRelationConfigs, + GetPgResourceAttributeMap, GetPgResourceAttributes, GetPgResourceCodec, + GetPgResourceRelationConfigs, GetPgResourceRelations, PgCodec, - PgCodecRelation, + PgCodecAttributes, + PgCodecPolymorphism, + PgCodecRelationConfigName, PgRegistry, PgTypedExecutableStep, } from "../interfaces.js"; @@ -36,13 +50,23 @@ import { getFragmentAndCodecFromOrder, PgSelectStep } from "./pgSelect.js"; // const debugPlanVerbose = debugPlan.extend("verbose"); // const debugExecuteVerbose = debugExecute.extend("verbose"); -export interface PgSelectSinglePlanOptions { - fromRelation?: [PgSelectSingleStep, string]; +export interface AnyPgSelectSinglePlanOptions + extends PgSelectSinglePlanOptions {} +export interface PgSelectSinglePlanOptions { + fromRelation?: readonly [ + PgSelectSingleStep, + PgCodecRelationConfigName< + GetPgRegistryCodecRelationConfigs< + PgResourceRegistry, + PgResourceCodec + > + >, + ]; } // Types that only take a few bytes so adding them to the selection would be // cheap to do. -const CHEAP_ATTRIBUTE_TYPES = new Set([ +const CHEAP_ATTRIBUTE_TYPES = new Set([ TYPES.int2, TYPES.int, TYPES.bigint, @@ -55,6 +79,14 @@ const CHEAP_ATTRIBUTE_TYPES = new Set([ TYPES.timestamptz, ]); +export interface AnyPgSelectSingleStep extends PgSelectSingleStep {} +export interface DefaultPgSelectSingleStep extends PgSelectSingleStep {} +export type PgSelectSingleStepResource = U extends PgSelectSingleStep< + infer TResource +> + ? TResource + : never; + /** * Represents the single result of a unique PgSelectStep. This might be * retrieved explicitly by PgSelectStep.single(), or implicitly (via Grafast) @@ -63,18 +95,12 @@ const CHEAP_ATTRIBUTE_TYPES = new Set([ * such as `.get` and `.cursor` which can receive specific properties by * telling the PgSelectStep to select the relevant expressions. */ -export class PgSelectSingleStep< - TResource extends PgResource = PgResource, - > +export class PgSelectSingleStep extends UnbatchedExecutableStep< unknown[] /* What we return will be a tuple based on the values selected */ > implements - PgTypedExecutableStep< - TResource extends PgResource - ? UCodec - : never - >, + PgTypedExecutableStep>, EdgeCapableStep { static $$export = { @@ -95,12 +121,12 @@ export class PgSelectSingleStep< constructor( $class: PgSelectStep, $item: ExecutableStep, - private options: PgSelectSinglePlanOptions = Object.create(null), + private options: PgSelectSinglePlanOptions = Object.create(null), ) { super(); this.itemStepId = this.addDependency($item); this.resource = $class.resource; - this.pgCodec = this.resource.codec as GetPgResourceCodec; + this.pgCodec = this.resource.codec; this.mode = $class.mode; this.classStepId = $class.id; } @@ -151,15 +177,16 @@ export class PgSelectSingleStep< * Returns a plan representing a named attribute (e.g. column) from the class * (e.g. table). */ - get>( + public get< + TAttr extends PgCodecAttributeName< + PgCodecAttributes> + >, + >( attr: TAttr, ): PgClassExpressionStep< - GetPgResourceAttributes[TAttr] extends PgCodecAttribute< - infer UCodec, - any - > - ? UCodec - : never, + PgCodecAttributeCodec< + Extract, { name: TAttr }> + >, TResource > { if (this.mode === "aggregate") { @@ -171,8 +198,7 @@ export class PgSelectSingleStep< ); } const classPlan = this.getClassStep(); - const resourceAttribute: PgCodecAttribute | undefined = - this.resource.codec.attributes?.[attr as string]; + const resourceAttribute = this.resource.codec.attributes?.[attr]; if (!resourceAttribute && attr !== "") { throw new Error( `${this.resource} does not define an attribute named '${String(attr)}'`, @@ -182,18 +208,18 @@ export class PgSelectSingleStep< if (resourceAttribute?.via) { const { relation, attribute } = this.resource.resolveVia( resourceAttribute.via, - attr as string, + attr, ); - return this.singleRelation(relation as any).get(attribute) as any; + return this.singleRelation(relation).get(attribute) as any; } if (resourceAttribute?.identicalVia) { const { relation, attribute } = this.resource.resolveVia( resourceAttribute.identicalVia, - attr as string, + attr, ); - const $existingPlan = this.existingSingleRelation(relation as any); + const $existingPlan = this.existingSingleRelation(relation); if ($existingPlan) { // Relation exists already; load it from there for efficiency return $existingPlan.get(attribute) as any; @@ -206,7 +232,10 @@ export class PgSelectSingleStep< const [$fromPlan, fromRelationName] = this.options.fromRelation; const matchingAttribute = ( Object.entries($fromPlan.resource.codec.attributes!) as Array< - [string, PgCodecAttribute] + [ + PgCodecAttributeName>, + AnyPgCodecAttribute, + ] > ).find(([name, col]) => { if (col.identicalVia) { @@ -221,7 +250,7 @@ export class PgSelectSingleStep< return false; }); if (matchingAttribute) { - return $fromPlan.get(matchingAttribute[0]) as any; + return $fromPlan.get(matchingAttribute[0]); } } @@ -236,7 +265,7 @@ export class PgSelectSingleStep< * decoding these string values. */ - const sqlExpr = pgClassExpression( + const sqlExpr = pgClassExpression( this, attr === "" ? this.resource.codec @@ -260,13 +289,13 @@ export class PgSelectSingleStep< this.nonNullAttribute = { attribute: resourceAttribute, attr }; } - return colPlan as any; + return colPlan; } /** * Returns a plan representing the result of an expression. */ - public select( + public select( fragment: SQL, codec: TExpressionCodec, ): PgClassExpressionStep { @@ -285,10 +314,13 @@ export class PgSelectSingleStep< } public placeholder($step: PgTypedExecutableStep): SQL; - public placeholder($step: ExecutableStep, codec: PgCodec): SQL; - public placeholder( + public placeholder( + $step: ExecutableStep, + codec: TCodec, + ): SQL; + public placeholder( $step: ExecutableStep | PgTypedExecutableStep, - overrideCodec?: PgCodec, + overrideCodec?: TCodec, ): SQL { return overrideCodec ? this.getClassStep().placeholder($step, overrideCodec) @@ -323,7 +355,9 @@ export class PgSelectSingleStep< } public singleRelation< - TRelationName extends keyof GetPgResourceRelations, + TRelationName extends PgCodecRelationConfigName< + GetPgResourceRelationConfigs + >, >( relationIdentifier: TRelationName, ): PgSelectSingleStep< @@ -333,9 +367,7 @@ export class PgSelectSingleStep< if ($existingPlan) { return $existingPlan; } - const relation = this.resource.getRelation( - relationIdentifier, - ) as PgCodecRelation; + const relation = this.resource.getRelation(relationIdentifier); if (!relation || !relation.isUnique) { throw new Error( `${String(relationIdentifier)} is not a unique relation on ${ @@ -345,11 +377,8 @@ export class PgSelectSingleStep< } const { remoteResource, remoteAttributes, localAttributes } = relation; - const options: PgSelectSinglePlanOptions = { - fromRelation: [ - this as PgSelectSingleStep, - relationIdentifier as string, - ], + const options = { + fromRelation: [this, relationIdentifier] as const, }; return remoteResource.get( remoteAttributes.reduce((memo, remoteAttribute, attributeIndex) => { @@ -357,19 +386,17 @@ export class PgSelectSingleStep< return memo; }, Object.create(null)), options, - ) as PgSelectSingleStep; + ); } public manyRelation< - TRelationName extends keyof GetPgResourceRelations, + TRelationName extends GetPgResourceRelationConfigs["name"], >( relationIdentifier: TRelationName, ): PgSelectStep< GetPgResourceRelations[TRelationName]["remoteResource"] > { - const relation = this.resource.getRelation( - relationIdentifier, - ) as PgCodecRelation; + const relation = this.resource.getRelation(relationIdentifier); if (!relation) { throw new Error( `${String(relationIdentifier)} is not a relation on ${this.resource}`, @@ -377,12 +404,12 @@ export class PgSelectSingleStep< } const { remoteResource, remoteAttributes, localAttributes } = relation; - return (remoteResource as PgResource).find( + return remoteResource.find( remoteAttributes.reduce((memo, remoteAttribute, attributeIndex) => { memo[remoteAttribute] = this.get(localAttributes[attributeIndex]); return memo; }, Object.create(null)), - ) as any; + ); } public record(): PgClassExpressionStep< @@ -456,7 +483,7 @@ export class PgSelectSingleStep< } planForType(type: GraphQLObjectType): ExecutableStep { - const poly = (this.resource.codec as PgCodec).polymorphism; + const poly: PgCodecPolymorphism = this.resource.codec.polymorphism; if (poly?.mode === "single") { return this; } else if (poly?.mode === "relational") { @@ -476,12 +503,12 @@ export class PgSelectSingleStep< } private nonNullAttribute: { - attribute: PgCodecAttribute; + attribute: AnyPgCodecAttribute; attr: string; } | null = null; private nullCheckAttributeIndex: number | null = null; optimize() { - const poly = (this.resource.codec as PgCodec).polymorphism; + const poly: PgCodecPolymorphism = this.resource.codec.polymorphism; if (poly?.mode === "single" || poly?.mode === "relational") { const $class = this.getClassStep(); this.typeStepIndexList = poly.typeAttributes.map((col) => { @@ -565,16 +592,18 @@ export class PgSelectSingleStep< unbatchedExecute( _extra: ExecutionExtra, - result: ObjectFromPgCodecAttributes>, + result: ObjectFromPgCodecAttributes>, ): unknown[] { if (result == null) { return this._coalesceToEmptyObject ? Object.create(null) : null; } else if (this.nullCheckAttributeIndex != null) { + // @ts-expect-error const nullIfAttributeNull = result[this.nullCheckAttributeIndex]; if (nullIfAttributeNull == null) { return this._coalesceToEmptyObject ? Object.create(null) : null; } } else if (this.nullCheckId != null) { + // @ts-expect-error const nullIfExpressionNotTrue = result[this.nullCheckId]; if ( nullIfExpressionNotTrue == null || @@ -597,7 +626,7 @@ export function pgSelectFromRecord< PgCodec, any, any, - PgRegistry + PgRegistry >, >( resource: TResource, @@ -616,9 +645,7 @@ export function pgSelectFromRecord< * Given a plan that represents a single record (via * PgSelectSingleStep.record()) this turns it back into a PgSelectSingleStep */ -export function pgSelectSingleFromRecord< - TResource extends PgResource, ->( +export function pgSelectSingleFromRecord( resource: TResource, $record: PgClassExpressionStep, TResource>, ): PgSelectSingleStep { diff --git a/grafast/dataplan-pg/src/steps/pgTempTable.ts b/grafast/dataplan-pg/src/steps/pgTempTable.ts index 7427af7ee9..35ee8d10c6 100644 --- a/grafast/dataplan-pg/src/steps/pgTempTable.ts +++ b/grafast/dataplan-pg/src/steps/pgTempTable.ts @@ -3,15 +3,13 @@ import { BaseStep } from "grafast"; import type { SQL } from "pg-sql2"; import { sql } from "pg-sql2"; -import type { PgResource } from "../datasource.js"; +import type { AnyPgResource } from "../datasource.js"; import type { PgClassFilterStep } from "../filters/pgClassFilter.js"; -import type { PgCodec } from "../interfaces.js"; +import type { AnyPgCodec } from "../interfaces.js"; import type { PgConditionCapableParentStep } from "./pgCondition.js"; import { PgConditionStep } from "./pgCondition.js"; -export class PgTempTableStep< - TResource extends PgResource, - > +export class PgTempTableStep extends BaseStep implements PgConditionCapableParentStep { @@ -30,7 +28,10 @@ export class PgTempTableStep< this.alias = sql.identifier(Symbol(`${resource.name}_filter`)); } - placeholder($step: ExecutableStep, codec: PgCodec): SQL { + placeholder( + $step: ExecutableStep, + codec: TCodec, + ): SQL { return this.$parent.placeholder($step, codec); } diff --git a/grafast/dataplan-pg/src/steps/pgUnionAll.ts b/grafast/dataplan-pg/src/steps/pgUnionAll.ts index 2ff0e8df35..aaf33300f7 100644 --- a/grafast/dataplan-pg/src/steps/pgUnionAll.ts +++ b/grafast/dataplan-pg/src/steps/pgUnionAll.ts @@ -30,13 +30,21 @@ import type { GraphQLObjectType } from "grafast/graphql"; import type { SQL, SQLRawValue } from "pg-sql2"; import { $$symbolToIdentifier, sql } from "pg-sql2"; -import type { PgCodecAttributes } from "../codecs.js"; import { TYPES } from "../codecs.js"; -import type { PgResource, PgResourceUnique } from "../datasource.js"; +import type { + AnyPgResource, + AnyPgResourceUnique, + PgResource, + PgResourceUnique, +} from "../datasource.js"; import type { PgExecutor } from "../executor.js"; -import type { PgCodecRefPath, PgCodecRelation, PgGroupSpec } from "../index.js"; import type { - PgCodec, + GetPgResourceAttributes, + PgCodecRefPath, + PgGroupSpec, +} from "../index.js"; +import type { + AnyPgCodec, PgOrderFragmentSpec, PgOrderSpec, PgTypedExecutableStep, @@ -121,7 +129,7 @@ type PgUnionAllStepSelect = | { type: "expression"; expression: SQL; - codec: PgCodec; + codec: AnyPgCodec; } | { type: "outerExpression"; @@ -130,19 +138,13 @@ type PgUnionAllStepSelect = export type PgUnionAllStepConfigAttributes = { [attributeName in TAttributes]: { - codec: PgCodec; + codec: AnyPgCodec; }; }; export interface PgUnionAllStepMember { typeName: TTypeNames; - resource: PgResource< - any, - any, - ReadonlyArray>, - any, - any - >; + resource: PgResource; match?: { [resourceAttributeName: string]: | { @@ -151,7 +153,7 @@ export interface PgUnionAllStepMember { } | { step: ExecutableStep; - codec: PgCodec; + codec: AnyPgCodec; }; }; path?: PgCodecRefPath; @@ -162,7 +164,7 @@ export interface PgUnionAllStepConfig< TTypeNames extends string, > { resourceByTypeName: { - [typeName in TTypeNames]: PgResource; + [typeName in TTypeNames]: AnyPgResource; }; attributes?: PgUnionAllStepConfigAttributes; members?: PgUnionAllStepMember[]; @@ -182,7 +184,7 @@ export interface PgUnionAllStepOrder { interface QueryValue { dependencyIndex: number; - codec: PgCodec; + codec: AnyPgCodec; alreadyEncoded: boolean; } @@ -198,7 +200,7 @@ interface QueryValue { */ type PgUnionAllPlaceholder = { dependencyIndex: number; - codec: PgCodec; + codec: AnyPgCodec; symbol: symbol; alreadyEncoded: boolean; }; @@ -239,9 +241,11 @@ export class PgUnionAllSingleStep // This type isn't handled; so it should never occur return constant(null); } - const pk = (resource.uniques as PgResourceUnique[] | undefined)?.find( - (u) => u.isPrimary === true, - ); + const pk = ( + resource.uniques as Array< + PgResourceUnique> + > + )?.find((u) => u.isPrimary === true); if (!pk) { throw new Error( `No PK found for ${objectType.name}; this should have been caught earlier?!`, @@ -298,7 +302,7 @@ export class PgUnionAllSingleStep /** * Returns a plan representing the result of an expression. */ - expression( + expression( expression: SQL, codec: TExpressionCodec, ): PgClassExpressionStep { @@ -315,7 +319,7 @@ export class PgUnionAllSingleStep return this.getClassStep().selectAndReturnIndex(fragment); } - public select( + public select( fragment: SQL, codec: TExpressionCodec, ): PgClassExpressionStep { @@ -341,13 +345,7 @@ export class PgUnionAllSingleStep interface MemberDigest { member: PgUnionAllStepMember; - finalResource: PgResource< - any, - any, - ReadonlyArray>, - any, - any - >; + finalResource: PgResource; sqlSource: SQL; symbol: symbol; alias: SQL; @@ -377,18 +375,24 @@ function cloneDigests( return digests.map(cloneDigest); } +export interface AnyPgUnionAllStep extends PgUnionAllStep {} + /** * Represents a `UNION ALL` statement, which can have multiple table-like * resources, but must return a consistent data shape. */ export class PgUnionAllStep< - TAttributes extends string = string, - TTypeNames extends string = string, + TAttributes extends string, + TTypeNames extends string, > extends ExecutableStep implements - ConnectionCapableStep, PgSelectParsedCursorStep> + ConnectionCapableStep< + PgSelectSingleStep, + PgSelectParsedCursorStep + > { + _PgUnionAllStep: "PgUnionAllStep" = "PgUnionAllStep"; static $$export = { moduleName: "@dataplan/pg", exportName: "PgUnionAllStep", @@ -675,9 +679,7 @@ export class PgUnionAllStep< let sqlSource = sql`${currentResource.from} as ${currentAlias}`; for (const pathEntry of path) { - const relation = currentResource.getRelation( - pathEntry.relationName, - ) as PgCodecRelation; + const relation = currentResource.getRelation(pathEntry.relationName); const nextResource = relation.remoteResource; const nextSymbol = Symbol(nextResource.name); const nextAlias = sql.identifier(nextSymbol); @@ -820,7 +822,10 @@ on (${sql.indent( return index; } - selectExpression(expression: SQL, codec: PgCodec): number { + selectExpression( + expression: SQL, + codec: TCodec, + ): number { const existingIndex = this.selects.findIndex( (s) => s.type === "expression" && sql.isEquivalent(s.expression, expression), @@ -1010,12 +1015,12 @@ on (${sql.indent( public placeholder($step: PgTypedExecutableStep): SQL; public placeholder( $step: ExecutableStep, - codec: PgCodec, + codec: AnyPgCodec, alreadyEncoded?: boolean, ): SQL; public placeholder( $step: ExecutableStep | PgTypedExecutableStep, - overrideCodec?: PgCodec, + overrideCodec?: AnyPgCodec, alreadyEncoded = false, ): SQL { if (this.locker.locked) { @@ -1128,7 +1133,7 @@ on (${sql.indent( ); identifierPlaceholders[i] = this.placeholder( toPg(access($parsedCursorPlan, [i + 1]), codec), - codec as PgCodec, + codec, ); } else { // No implementations?! @@ -1143,7 +1148,7 @@ on (${sql.indent( } const max = orderCount - 1 + pk.attributes.length; const pkPlaceholder = identifierPlaceholders[orderCount - 1]; - const pkAttributes = finalResource.codec.attributes as PgCodecAttributes; + const pkAttributes = finalResource.codec.attributes; const condition = (i = 0): SQL => { const order = digest.orders[i]; const [ @@ -1562,7 +1567,7 @@ and ${condition(i + 1)}`} const midSelects: SQL[] = []; const innerSelects = this.selects .map((s, selectIndex) => { - const r = ((): [SQL, PgCodec] | null => { + const r = ((): [SQL, AnyPgCodec] | null => { switch (s.type) { case "attribute": { if (!this.spec.attributes) { diff --git a/grafast/dataplan-pg/src/steps/pgUpdateSingle.ts b/grafast/dataplan-pg/src/steps/pgUpdateSingle.ts index d2b58662ce..31f59f77b4 100644 --- a/grafast/dataplan-pg/src/steps/pgUpdateSingle.ts +++ b/grafast/dataplan-pg/src/steps/pgUpdateSingle.ts @@ -7,18 +7,19 @@ import { ExecutableStep, exportAs, isDev, SafeError, setter } from "grafast"; import type { SQL, SQLRawValue } from "pg-sql2"; import sql from "pg-sql2"; -import type { PgCodecAttribute } from "../codecs.js"; -import type { PgResource, PgResourceUnique } from "../index.js"; +import type { AnyPgCodecAttribute, PgCodecAttributeName } from "../codecs.js"; +import type { DefaultPgResource, PgResourceUnique } from "../index.js"; import { inspect } from "../inspect.js"; import type { + AnyPgCodec, GetPgResourceAttributes, GetPgResourceCodec, GetPgResourceUniques, - PgCodec, PlanByUniques, } from "../interfaces.js"; import type { PgClassExpressionStep } from "./pgClassExpression.js"; import { pgClassExpression } from "./pgClassExpression.js"; +import { AnyPgResource, PgResourceUniques } from "../datasource.js"; type QueryValueDetailsBySymbol = Map< symbol, @@ -35,12 +36,14 @@ interface PgUpdatePlanFinalizeResults { /** When we see the given symbol in the SQL values, what dependency do we replace it with? */ queryValueDetailsBySymbol: QueryValueDetailsBySymbol; } - +export interface AnyPgUpdateSingleStep extends PgUpdateSingleStep {} +export interface DefaultPgUpdateSingleStep + extends PgUpdateSingleStep {} /** * Update a single row identified by the 'getBy' argument. */ export class PgUpdateSingleStep< - TResource extends PgResource = PgResource, + TResource extends AnyPgResource, > extends ExecutableStep { static $$export = { moduleName: "@dataplan/pg", @@ -75,18 +78,18 @@ export class PgUpdateSingleStep< * The attributes and their dependency ids for us to find the record by. */ private getBys: Array<{ - name: keyof GetPgResourceAttributes; + name: PgCodecAttributeName>; depId: number; - pgCodec: PgCodec; + pgCodec: AnyPgCodec; }> = []; /** * The attributes and their dependency ids for us to update. */ private attributes: Array<{ - name: keyof GetPgResourceAttributes; + name: PgCodecAttributeName>; depId: number; - pgCodec: PgCodec; + pgCodec: AnyPgCodec; }> = []; /** @@ -114,10 +117,10 @@ export class PgUpdateSingleStep< resource: TResource, getBy: PlanByUniques< GetPgResourceAttributes, - GetPgResourceUniques + PgResourceUniques >, attributes?: { - [key in keyof GetPgResourceAttributes]?: ExecutableStep; // | PgTypedExecutableStep + [attribute in GetPgResourceAttributes as PgCodecAttributeName]?: ExecutableStep; // | PgTypedExecutableStep }, ) { super(); @@ -127,14 +130,18 @@ export class PgUpdateSingleStep< this.alias = sql.identifier(this.symbol); this.contextId = this.addDependency(this.resource.executor.context()); - const keys: ReadonlyArray> = getBy - ? (Object.keys(getBy) as Array>) + const keys = getBy + ? (Object.keys(getBy) as Array< + PgCodecAttributeName> + >) : []; if ( - !(this.resource.uniques as PgResourceUnique[]).some((uniq) => - uniq.attributes.every((key) => keys.includes(key as any)), - ) + !( + this.resource.uniques as Array< + PgResourceUnique> + > + ).some((uniq) => uniq.attributes.every((key) => keys.includes(key))) ) { throw new Error( `Attempted to build 'PgUpdateSingleStep' with a non-unique getBy keys ('${keys.join( @@ -155,22 +162,26 @@ export class PgUpdateSingleStep< ); } } - const value = (getBy as any)![name as any]; + const value = getBy[name]; const depId = this.addDependency(value); - const attribute = ( - this.resource.codec.attributes as GetPgResourceAttributes - )[name]; - const pgCodec = attribute.codec; - this.getBys.push({ name, depId, pgCodec }); + const attribute = this.resource.codec.attributes?.[name]; + if (attribute) { + const pgCodec = attribute.codec; + this.getBys.push({ name, depId, pgCodec }); + } }); if (attributes) { - Object.entries(attributes).forEach(([key, value]) => { + ( + Object.entries(attributes) as Array< + [ + PgCodecAttributeName>, + ExecutableStep, + ] + > + ).forEach(([key, value]) => { if (value) { - this.set( - key as keyof GetPgResourceAttributes, - value as ExecutableStep, - ); + this.set(key, value as ExecutableStep); } }); } @@ -182,9 +193,9 @@ export class PgUpdateSingleStep< )};${this.attributes.map((a) => a.name)})`; } - set>( + set>>( name: TKey, - value: ExecutableStep, // | PgTypedExecutableStep + value: ExecutableStep, // PgTypedExecutableStep[number]["codec"]>, ): void { if (this.locked) { throw new Error("Cannot set after plan is locked."); @@ -196,17 +207,16 @@ export class PgUpdateSingleStep< ); } } - const { codec: pgCodec } = ( - this.resource.codec.attributes as GetPgResourceAttributes - )[name]; - const depId = this.addDependency(value); - this.attributes.push({ name, depId, pgCodec }); + const attribute = this.resource.codec.attributes?.[name]; + if (attribute) { + const depId = this.addDependency(value); + this.attributes.push({ name, depId, pgCodec: attribute.codec }); + } } setPlan(): SetterStep< { - [key in keyof GetPgResourceAttributes & - string]: ExecutableStep; + [attribute in GetPgResourceAttributes as PgCodecAttributeName]: ExecutableStep; }, this > { @@ -222,13 +232,13 @@ export class PgUpdateSingleStep< * Returns a plan representing a named attribute (e.g. column) from the newly * updateed row. */ - get>( + get>>( attr: TAttr, ): PgClassExpressionStep< - GetPgResourceAttributes[TAttr]["codec"], + Extract, { name: TAttr }>["codec"], TResource > { - const resourceAttribute: PgCodecAttribute = + const resourceAttribute: AnyPgCodecAttribute = this.resource.codec.attributes![attr as string]; if (!resourceAttribute) { throw new Error( @@ -450,16 +460,14 @@ export class PgUpdateSingleStep< /** * Update a single row identified by the 'getBy' argument. */ -export function pgUpdateSingle< - TResource extends PgResource, ->( +export function pgUpdateSingle( resource: TResource, getBy: PlanByUniques< GetPgResourceAttributes, GetPgResourceUniques >, attributes?: { - [key in keyof GetPgResourceAttributes]?: ExecutableStep; // | PgTypedExecutableStep + [attribute in GetPgResourceAttributes as PgCodecAttributeName]?: ExecutableStep; // | PgTypedExecutableStep }, ): PgUpdateSingleStep { return new PgUpdateSingleStep(resource, getBy, attributes); diff --git a/grafast/dataplan-pg/src/steps/toPg.ts b/grafast/dataplan-pg/src/steps/toPg.ts index 2f002d460a..b970306191 100644 --- a/grafast/dataplan-pg/src/steps/toPg.ts +++ b/grafast/dataplan-pg/src/steps/toPg.ts @@ -1,7 +1,7 @@ import type { ExecutableStep, ExecutionExtra } from "grafast"; import { UnbatchedExecutableStep } from "grafast"; -import type { PgCodec } from "../interfaces.js"; +import type { AnyPgCodec, PgCodec } from "../interfaces.js"; /** * Converts the given value to the representation suitable for feeding into the @@ -19,7 +19,7 @@ export class ToPgStep extends UnbatchedExecutableStep { isSyncAndSafe = true; constructor( $value: ExecutableStep, - private codec: PgCodec, + private codec: AnyPgCodec, ) { super(); this.addDependency($value); @@ -39,6 +39,6 @@ export class ToPgStep extends UnbatchedExecutableStep { * * @internal */ -export function toPg($value: ExecutableStep, codec: PgCodec) { +export function toPg($value: ExecutableStep, codec: AnyPgCodec) { return new ToPgStep($value, codec); } diff --git a/graphile-build/graphile-build-pg/src/examples/NO_DATA_GATHERING.ts b/graphile-build/graphile-build-pg/src/examples/NO_DATA_GATHERING.ts index 240c05e68e..b3ead311ae 100644 --- a/graphile-build/graphile-build-pg/src/examples/NO_DATA_GATHERING.ts +++ b/graphile-build/graphile-build-pg/src/examples/NO_DATA_GATHERING.ts @@ -395,7 +395,7 @@ async function main() { // We're crafting our own input const input: GraphileBuild.BuildInput = { - pgRegistry: pgRegistry as unknown as PgRegistry, + pgRegistry, }; const schema = buildSchema(config, input); diff --git a/graphile-build/graphile-build-pg/src/inputUtils.ts b/graphile-build/graphile-build-pg/src/inputUtils.ts index 81589c891e..8d2a53f704 100644 --- a/graphile-build/graphile-build-pg/src/inputUtils.ts +++ b/graphile-build/graphile-build-pg/src/inputUtils.ts @@ -1,6 +1,6 @@ import "graphile-build"; -import type { PgCodec, PgCodecRelation, PgResource } from "@dataplan/pg"; +import type { DefaultPgCodec, DefaultPgResource } from "@dataplan/pg"; /** * Metadata for a specific PgCodec @@ -18,12 +18,12 @@ export interface PgCodecMeta { /** * A map from PgCodec to its associated metadata. */ -export type PgCodecMetaLookup = Map; +export type PgCodecMetaLookup = Map; /** * Creates an empty meta object for the given codec. */ -export function makePgCodecMeta(_codec: PgCodec): PgCodecMeta { +export function makePgCodecMeta(_codec: DefaultPgCodec): PgCodecMeta { return { typeNameBySituation: Object.create(null), }; @@ -40,8 +40,8 @@ export function getCodecMetaLookupFromInput( input: GraphileBuild.BuildInput, ): PgCodecMetaLookup { const metaLookup: PgCodecMetaLookup = new Map(); - const seenResources = new Set(); - for (const codec of Object.values(input.pgRegistry.pgCodecs) as PgCodec[]) { + const seenResources = new Set(); + for (const codec of Object.values(input.pgRegistry.pgCodecs)) { walkCodec(codec, metaLookup); } for (const resource of Object.values(input.pgRegistry.pgResources)) { @@ -57,9 +57,9 @@ export function getCodecMetaLookupFromInput( * @internal */ function walkResource( - resource: PgResource, + resource: DefaultPgResource, metaLookup: PgCodecMetaLookup, - seenResources: Set, + seenResources: Set, ): void { if (seenResources.has(resource)) { return; @@ -68,7 +68,7 @@ function walkResource( if (!metaLookup.has(resource.codec)) { walkCodec(resource.codec, metaLookup); } - const relations = resource.getRelations() as Record; + const relations = resource.getRelations(); if (relations) { for (const relationshipName in relations) { walkResource( @@ -86,7 +86,7 @@ function walkResource( * * @internal */ -function walkCodec(codec: PgCodec, metaLookup: PgCodecMetaLookup): void { +function walkCodec(codec: DefaultPgCodec, metaLookup: PgCodecMetaLookup): void { if (metaLookup.has(codec)) { return; } diff --git a/graphile-build/graphile-build-pg/src/interfaces.ts b/graphile-build/graphile-build-pg/src/interfaces.ts index 0796780f1c..5d64224b5b 100644 --- a/graphile-build/graphile-build-pg/src/interfaces.ts +++ b/graphile-build/graphile-build-pg/src/interfaces.ts @@ -1,4 +1,4 @@ -import type { PgRegistry, WithPgClient } from "@dataplan/pg"; +import type { DefaultPgRegistry, PgRegistry, WithPgClient } from "@dataplan/pg"; import type { PromiseOrDirect } from "grafast"; declare global { namespace GraphileBuild { @@ -107,7 +107,7 @@ declare global { namespace GraphileBuild { interface BuildInput { - pgRegistry: PgRegistry; + pgRegistry: DefaultPgRegistry; } } } diff --git a/graphile-build/graphile-build-pg/src/plugins/PgAttributesPlugin.ts b/graphile-build/graphile-build-pg/src/plugins/PgAttributesPlugin.ts index 83ec4ea39a..3eafa0d535 100644 --- a/graphile-build/graphile-build-pg/src/plugins/PgAttributesPlugin.ts +++ b/graphile-build/graphile-build-pg/src/plugins/PgAttributesPlugin.ts @@ -3,12 +3,11 @@ import "../interfaces.js"; import "graphile-config"; import type { + DefaultPgCodec, + DefaultPgCodecAttribute, + DefaultPgSelectSingleStep, PgClassExpressionStep, - PgCodec, - PgCodecAttribute, - PgCodecAttributes, PgCodecList, - PgCodecWithAttributes, PgConditionStep, PgSelectSingleStep, PgSelectStep, @@ -25,9 +24,9 @@ declare global { namespace GraphileBuild { interface Build { pgResolveOutputType( - codec: PgCodec, + codec: DefaultPgCodec, notNull?: boolean, - ): [baseCodec: PgCodec, resolvedType: GraphQLOutputType] | null; + ): [baseCodec: DefaultPgCodec, resolvedType: GraphQLOutputType] | null; } interface Inflection { @@ -42,7 +41,7 @@ declare global { _attributeName( this: GraphileBuild.Inflection, details: { - codec: PgCodecWithAttributes; + codec: DefaultPgCodec; attributeName: string; skipRowId?: boolean; }, @@ -54,7 +53,7 @@ declare global { */ _joinAttributeNames( this: GraphileBuild.Inflection, - codec: PgCodecWithAttributes, + codec: DefaultPgCodec, names: readonly string[], ): string; @@ -67,7 +66,7 @@ declare global { this: GraphileBuild.Inflection, details: { attributeName: string; - codec: PgCodecWithAttributes; + codec: DefaultPgCodec; }, ): string; } @@ -78,7 +77,7 @@ declare global { isPgBaseInput?: boolean; isPgRowType?: boolean; isPgCompoundType?: boolean; - pgAttribute?: PgCodecAttribute; + pgAttribute?: DefaultPgCodecAttribute; } } } @@ -102,11 +101,11 @@ function processAttribute( return; } - const pgCodec = rawPgCodec as PgCodecWithAttributes; + const pgCodec = rawPgCodec; const isInterface = context.type === "GraphQLInterfaceType"; - const attribute = pgCodec.attributes[attributeName]; + const attribute = pgCodec.attributes![attributeName]; if ( !build.behavior.pgCodecAttributeMatches( @@ -147,7 +146,7 @@ function processAttribute( }; const resource = baseCodec.attributes - ? build.pgTableResource(baseCodec as PgCodecWithAttributes, false) + ? build.pgTableResource(baseCodec, false) : null; if (baseCodec.attributes && !resource) { // We can't load codecs with attributes unless we know the executor. @@ -160,7 +159,7 @@ function processAttribute( if (!baseCodec.attributes) { // Simply get the value return EXPORTABLE( - (attributeName) => ($record: PgSelectSingleStep) => { + (attributeName) => ($record: DefaultPgSelectSingleStep) => { return $record.get(attributeName); }, [attributeName], @@ -257,7 +256,7 @@ export const PgAttributesPlugin: GraphileConfig.Plugin = { inflection: { add: { _attributeName(options, { attributeName, codec }) { - const attribute = codec.attributes[attributeName]; + const attribute = codec.attributes![attributeName]; return this.coerceToGraphQLName( attribute.extensions?.tags?.name || attributeName, ); @@ -290,8 +289,8 @@ export const PgAttributesPlugin: GraphileConfig.Plugin = { const behaviors = new Set([ "select base update insert filterBy orderBy", ]); - const attribute = codec.attributes[attributeName]; - function walk(codec: PgCodec) { + const attribute = codec.attributes![attributeName]; + function walk(codec: DefaultPgCodec) { if (codec.arrayOfCodec) { behaviors.add("-condition:attribute:filterBy"); behaviors.add(`-attribute:orderBy`); @@ -443,8 +442,8 @@ export const PgAttributesPlugin: GraphileConfig.Plugin = { ) { return fields; } - const pgCodec = rawPgCodec as PgCodecWithAttributes; - const allAttributes = pgCodec.attributes; + const pgCodec = rawPgCodec; + const allAttributes = pgCodec.attributes!; const allowedAttributes = pgCodec.polymorphism?.mode === "single" ? [ @@ -463,12 +462,12 @@ export const PgAttributesPlugin: GraphileConfig.Plugin = { */ ] : null; - const attributes = allowedAttributes - ? (Object.fromEntries( + const attributes = allowedAttributes! + ? Object.fromEntries( Object.entries(allAttributes).filter(([attrName, _attr]) => allowedAttributes.includes(attrName), ), - ) as PgCodecAttributes) + ) : allAttributes; return Object.entries(attributes).reduce( @@ -597,9 +596,9 @@ export const PgAttributesPlugin: GraphileConfig.Plugin = { function resolveOutputType( build: GraphileBuild.Build, - codec: PgCodec, + codec: DefaultPgCodec, notNull?: boolean, -): [baseCodec: PgCodec, resolvedType: GraphQLOutputType] | null { +): [baseCodec: DefaultPgCodec, resolvedType: GraphQLOutputType] | null { const { getGraphQLTypeByPgCodec, graphql: { GraphQLList, GraphQLNonNull, getNullableType, isOutputType }, diff --git a/graphile-build/graphile-build-pg/src/plugins/PgBasicsPlugin.ts b/graphile-build/graphile-build-pg/src/plugins/PgBasicsPlugin.ts index 134325096f..bc6691f20a 100644 --- a/graphile-build/graphile-build-pg/src/plugins/PgBasicsPlugin.ts +++ b/graphile-build/graphile-build-pg/src/plugins/PgBasicsPlugin.ts @@ -2,15 +2,7 @@ import "./PgTablesPlugin.js"; import "../interfaces.js"; import "graphile-config"; -import type { - PgCodec, - PgCodecRef, - PgCodecRelation, - PgCodecWithAttributes, - PgRefDefinition, - PgResource, - PgResourceUnique, -} from "@dataplan/pg"; +import type { PgCodecRef, PgRefDefinition, PgResource } from "@dataplan/pg"; import * as dataplanPg from "@dataplan/pg"; import type { GraphQLType } from "grafast/graphql"; import { EXPORTABLE, gatherConfig } from "graphile-build"; @@ -29,19 +21,19 @@ declare global { "@dataplan/pg": string; } type HasGraphQLTypeForPgCodec = ( - codec: PgCodec, + codec: dataplanPg.DefaultPgCodec, situation?: string, ) => boolean; type GetGraphQLTypeByPgCodec = ( - codec: PgCodec, + codec: dataplanPg.DefaultPgCodec, situation: string, ) => GraphQLType | null; type GetGraphQLTypeNameByPgCodec = ( - codec: PgCodec, + codec: dataplanPg.DefaultPgCodec, situation: string, ) => string | null; type SetGraphQLTypeForPgCodec = ( - codec: PgCodec, + codec: dataplanPg.DefaultPgCodec, situations: string | string[], typeName: string, ) => void; @@ -92,26 +84,30 @@ declare global { /** * Get a table-like resource for the given codec, assuming exactly one exists. */ - pgTableResource( + pgTableResource( codec: TCodec, strict?: boolean, ): PgResource< string, TCodec, - ReadonlyArray, - undefined + dataplanPg.DefaultPgResourceUnique, + never, + dataplanPg.DefaultPgRegistry > | null; } interface BehaviorEntities { - pgCodec: PgCodec; - pgCodecAttribute: [codec: PgCodecWithAttributes, attributeName: string]; - pgResource: PgResource; + pgCodec: dataplanPg.DefaultPgCodec; + pgCodecAttribute: [ + codec: dataplanPg.DefaultPgCodec, + attributeName: string, + ]; + pgResource: dataplanPg.DefaultPgResource; pgResourceUnique: [ - resource: PgResource, - unique: PgResourceUnique, + resource: dataplanPg.DefaultPgResource, + unique: dataplanPg.DefaultPgResourceUnique, ]; - pgCodecRelation: PgCodecRelation; + pgCodecRelation: dataplanPg.DefaultPgRelation; pgCodecRef: PgCodecRef; pgRefDefinition: PgRefDefinition; } @@ -190,7 +186,7 @@ export const PgBasicsPlugin: GraphileConfig.Plugin = { `pgCodecAttribute no longer accepts (codec, attribute) - it now accepts (codec, attributeName). Please update your code. Sorry! (Changed in PostGraphile V5 alpha 13.)`, ); } - const attribute = codec.attributes[attributeName]; + const attribute = codec.attributes![attributeName]; return [ behavior, getBehavior([codec.extensions, attribute.extensions]), @@ -334,34 +330,41 @@ export const PgBasicsPlugin: GraphileConfig.Plugin = { } }; const resourceByCodecCacheUnstrict = new Map< - PgCodecWithAttributes, - PgResource | null + dataplanPg.DefaultPgCodec, + dataplanPg.DefaultPgResource | null >(); const resourceByCodecCacheStrict = new Map< - PgCodecWithAttributes, - PgResource | null + dataplanPg.DefaultPgCodec, + dataplanPg.DefaultPgResource | null >(); - const pgTableResource = ( + const pgTableResource = ( codec: TCodec, strict = true, ): PgResource< string, TCodec, - ReadonlyArray, - undefined + dataplanPg.DefaultPgResourceUnique, + never, + dataplanPg.DefaultPgRegistry > | null => { const resourceByCodecCache = strict ? resourceByCodecCacheStrict : resourceByCodecCacheUnstrict; if (resourceByCodecCache.has(codec)) { - return resourceByCodecCache.get(codec)!; + return resourceByCodecCache.get(codec)! as any; } const resources = Object.values( build.input.pgRegistry.pgResources, ).filter( ( - r: PgResource, - ): r is PgResource => + r, + ): r is PgResource< + string, + TCodec, + any, + never, + dataplanPg.DefaultPgRegistry + > => r.codec === codec && !r.parameters && r.executor === codec.executor && diff --git a/graphile-build/graphile-build-pg/src/plugins/PgCodecsPlugin.ts b/graphile-build/graphile-build-pg/src/plugins/PgCodecsPlugin.ts index 958823516b..8c685db40b 100644 --- a/graphile-build/graphile-build-pg/src/plugins/PgCodecsPlugin.ts +++ b/graphile-build/graphile-build-pg/src/plugins/PgCodecsPlugin.ts @@ -1,7 +1,9 @@ import type { + AnyPgCodecAttributesRecord, + DefaultPgCodec, + DefaultPgCodecAttribute, PgCodec, PgCodecAnyScalar, - PgCodecAttribute, PgCodecAttributes, PgCodecExtensions, PgEnumCodec, @@ -27,11 +29,11 @@ import { version } from "../version.js"; interface State { codecByTypeIdByDatabaseName: Map< string, - Map> + Map> >; codecByClassIdByDatabaseName: Map< string, - Map> + Map> >; } @@ -63,7 +65,7 @@ declare global { } interface Build { - allPgCodecs: Set; + allPgCodecs: Set; } interface ScopeObject { @@ -74,7 +76,7 @@ declare global { interface ScopeInputObject { isPgRangeInputType?: boolean; isPgRangeBoundInputType?: boolean; - pgCodec?: PgCodec; + pgCodec?: DefaultPgCodec; } } @@ -84,18 +86,18 @@ declare global { getCodecFromClass( serviceName: string, pgClassId: string, - ): Promise; + ): Promise; getCodecFromType( serviceName: string, pgTypeId: string, pgTypeModifier?: string | number | null, - ): Promise; + ): Promise; }; } interface GatherHooks { pgCodecs_PgCodec(event: { serviceName: string; - pgCodec: PgCodec; + pgCodec: DefaultPgCodec; pgClass?: PgClass; pgType: PgType; }): Promise | void; @@ -104,7 +106,7 @@ declare global { serviceName: string; pgClass: PgClass; pgAttribute: PgAttribute; - attribute: PgCodecAttribute; + attribute: DefaultPgCodecAttribute; }): Promise | void; pgCodecs_recordType_spec(event: { @@ -122,21 +124,21 @@ declare global { pgCodecs_rangeOfCodec_extensions(event: { serviceName: string; pgType: PgType; - innerCodec: PgCodec; + innerCodec: DefaultPgCodec; extensions: any; }): Promise | void; pgCodecs_domainOfCodec_extensions(event: { serviceName: string; pgType: PgType; - innerCodec: PgCodec; + innerCodec: DefaultPgCodec; extensions: any; }): Promise | void; pgCodecs_listOfCodec_extensions(event: { serviceName: string; pgType: PgType; - innerCodec: PgCodec; + innerCodec: DefaultPgCodec; extensions: any; }): Promise | void; } @@ -308,7 +310,7 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { ); } - const attributes: PgCodecAttributes = Object.create(null); + const attributes: AnyPgCodecAttributesRecord = Object.create(null); const allAttributes = await info.helpers.pgIntrospection.getAttributesForClass( serviceName, @@ -465,7 +467,7 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { return map.get(typeId)!; } - const promise = (async (): Promise => { + const promise = (async (): Promise => { const type = await info.helpers.pgIntrospection.getType( serviceName, typeId, @@ -502,7 +504,7 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { ); } - const codec = await (async (): Promise => { + const codec = await (async (): Promise => { const namespace = await info.helpers.pgIntrospection.getNamespace( serviceName, type.typnamespace, @@ -603,12 +605,10 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { `Failed to get range entry related to '${type._id}'`, ); } - const innerCodec = (await info.helpers.pgCodecs.getCodecFromType( + const innerCodec = await info.helpers.pgCodecs.getCodecFromType( serviceName, range.rngsubtype!, - )) as - | PgCodec - | undefined; + ); const namespaceName = namespace.nspname; const typeName = type.typname; if (!innerCodec) { @@ -726,7 +726,7 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { description, extensions, notNull, - }) as PgCodec, + }), [ codecName, description, @@ -749,14 +749,11 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { ); if (innerType) { - const innerCodec = - (await info.helpers.pgCodecs.getCodecFromType( - serviceName, - innerType._id, - typeModifier, // TODO: is it correct to pass this through? - )) as - | PgCodec - | undefined; + const innerCodec = await info.helpers.pgCodecs.getCodecFromType( + serviceName, + innerType._id, + typeModifier, // TODO: is it correct to pass this through? + ); if (innerCodec) { const typeDelim = innerType.typdelim!; const { tags, description } = type.getTagsAndDescription(); @@ -830,7 +827,7 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { hooks: { async pgRegistry_PgRegistryBuilder_pgCodecs(info, event) { const { registryBuilder } = event; - const codecs = new Set(); + const codecs = new Set(); // If we get errors from the frozen object then clearly we need to // ensure more work has completed before continuing - call other plugin @@ -865,8 +862,8 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { schema: { hooks: { build(build) { - build.allPgCodecs = new Set(); - function walkCodec(codec: PgCodec): void { + build.allPgCodecs = new Set(); + function walkCodec(codec: DefaultPgCodec): void { if (build.allPgCodecs!.has(codec)) { return; } @@ -878,9 +875,7 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { if (codec.attributes) { for (const attributeName in codec.attributes) { - const attributeCodec = (codec.attributes as PgCodecAttributes)[ - attributeName - ].codec; + const attributeCodec = codec.attributes[attributeName].codec; walkCodec(attributeCodec); } } @@ -906,15 +901,13 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { } } if (build.input.pgRegistry.pgCodecs) { - for (const codec of Object.values( - build.input.pgRegistry.pgCodecs, - ) as PgCodec[]) { + for (const codec of Object.values(build.input.pgRegistry.pgCodecs)) { walkCodec(codec); } } // Ensure all sources are uniquely named - const knownCodecByName = new Map(); + const knownCodecByName = new Map(); for (const codec of build.allPgCodecs) { const known = knownCodecByName.get(codec.name); if (known === codec) { @@ -1090,8 +1083,8 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { // Now walk over all the codecs and ensure that each on has an associated type function prepareTypeForCodec( - codec: PgCodec, - visited: Set, + codec: DefaultPgCodec, + visited: Set, ): void { if (visited.has(codec)) { return; @@ -1109,9 +1102,7 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { // Process all the attributes (if any), then exit. if (codec.attributes) { for (const attributeName in codec.attributes) { - const attributeCodec = (codec.attributes as PgCodecAttributes)[ - attributeName - ].codec; + const attributeCodec = codec.attributes[attributeName].codec; prepareTypeForCodec(attributeCodec, visited); } @@ -1163,11 +1154,10 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { typeName, ); } else if (codec.rangeOfCodec || codec.domainOfCodec) { - const underlyingType = - codec.rangeOfCodec || + const underlyingType = (codec.rangeOfCodec || codec.domainOfCodec?.arrayOfCodec || codec.domainOfCodec?.rangeOfCodec || - codec.domainOfCodec; + codec.domainOfCodec)!; // This type is a "domain", so we can mimic the underlying type const underlyingOutputTypeName = build.getGraphQLTypeNameByPgCodec(underlyingType, "output"); @@ -1462,7 +1452,7 @@ export const PgCodecsPlugin: GraphileConfig.Plugin = { } } - const visited: Set = new Set(); + const visited: Set = new Set(); for (const codec of build.allPgCodecs) { prepareTypeForCodec(codec, visited); } diff --git a/graphile-build/graphile-build-pg/src/plugins/PgConditionArgumentPlugin.ts b/graphile-build/graphile-build-pg/src/plugins/PgConditionArgumentPlugin.ts index 730a07def5..7cd8878334 100644 --- a/graphile-build/graphile-build-pg/src/plugins/PgConditionArgumentPlugin.ts +++ b/graphile-build/graphile-build-pg/src/plugins/PgConditionArgumentPlugin.ts @@ -2,6 +2,8 @@ import "./PgTablesPlugin.js"; import "graphile-config"; import type { + DefaultPgSelectSingleStep, + DefaultPgSelectStep, PgCodecWithAttributes, PgSelectParsedCursorStep, PgSelectSingleStep, @@ -59,7 +61,7 @@ export const PgConditionArgumentPlugin: GraphileConfig.Plugin = { if (!rawCodec.attributes || rawCodec.isAnonymous) { return; } - const codec = rawCodec as PgCodecWithAttributes; + const codec = rawCodec; const tableTypeName = inflection.tableType(codec); const conditionName = inflection.conditionType(tableTypeName); @@ -156,15 +158,15 @@ export const PgConditionArgumentPlugin: GraphileConfig.Plugin = { ? ( _condition, $connection: ConnectionStep< - PgSelectSingleStep, + DefaultPgSelectSingleStep, PgSelectParsedCursorStep, - PgSelectStep + DefaultPgSelectStep >, ) => { const $select = $connection.getSubplan(); return $select.wherePlan(); } - : (_condition, $select: PgSelectStep) => { + : (_condition, $select: DefaultPgSelectStep) => { return $select.wherePlan(); }, }, diff --git a/graphile-build/graphile-build-pg/src/plugins/PgConditionCustomFieldsPlugin.ts b/graphile-build/graphile-build-pg/src/plugins/PgConditionCustomFieldsPlugin.ts index fcad8ee74f..06f5765aae 100644 --- a/graphile-build/graphile-build-pg/src/plugins/PgConditionCustomFieldsPlugin.ts +++ b/graphile-build/graphile-build-pg/src/plugins/PgConditionCustomFieldsPlugin.ts @@ -1,6 +1,7 @@ import "graphile-config"; import type { + DefaultPgResource, PgConditionStep, PgResource, PgResourceParameter, @@ -24,12 +25,11 @@ declare global { * arguments except the first are nullable, the first argument is a composite * type, and the result is a simple scalar type. */ -export function isSimpleScalarComputedColumnLike(resource: PgResource) { +export function isSimpleScalarComputedColumnLike(resource: DefaultPgResource) { if (resource.codec.attributes) return false; if (resource.codec.arrayOfCodec) return false; if (resource.codec.rangeOfCodec) return false; - const parameters: readonly PgResourceParameter[] | undefined = - resource.parameters; + const parameters = resource.parameters; if (!parameters || parameters.length < 1) return false; if (parameters.some((p, i) => i > 0 && p.required)) return false; if (!parameters[0].codec.attributes) return false; @@ -85,72 +85,69 @@ export const PgConditionCustomFieldsPlugin: GraphileConfig.Plugin = { return build.extend( fields, - functionSources.reduce((memo, rawPgFieldSource) => { - const pgFieldSource = rawPgFieldSource as PgResource< - any, - any, - any, - readonly PgResourceParameter[], - any - >; - const fieldName = inflection.computedAttributeField({ - resource: pgFieldSource, - }); - const type = build.getGraphQLTypeByPgCodec( - pgFieldSource.codec, - "input", - ); - if (!type) return memo; - memo = build.extend( - memo, - { - [fieldName]: fieldWithHooks( - { - fieldName, - fieldBehaviorScope: "proc:filterBy", - isPgConnectionConditionInputField: true, - pgFieldSource, - }, - { - description: build.wrapDescription( - `Checks for equality with the object’s \`${fieldName}\` field.`, - "field", - ), - type, - applyPlan: EXPORTABLE( - (pgFieldSource, sql) => - function plan( - $condition: PgConditionStep>, - val, - ) { - if (typeof pgFieldSource.from !== "function") { - throw new Error( - "Invalid computed attribute 'from'", - ); - } - const expression = sql`${pgFieldSource.from({ - placeholder: $condition.alias, - })}`; - if (val.getRaw().evalIs(null)) { - $condition.where(sql`${expression} is null`); - } else { - $condition.where( - sql`${expression} = ${$condition.placeholder( - val.get(), - pgFieldSource.codec, - )}`, - ); - } - }, - [pgFieldSource, sql], - ), - }, - ), - }, - `Adding computed attribute condition argument for ${pgCodec.name}`, - ); - return memo; - }, Object.create(null)), + functionSources.reduce( + (memo, rawPgFieldSource: DefaultPgResource) => { + const pgFieldSource = rawPgFieldSource; + const fieldName = inflection.computedAttributeField({ + resource: pgFieldSource, + }); + const type = build.getGraphQLTypeByPgCodec( + pgFieldSource.codec, + "input", + ); + if (!type) return memo; + memo = build.extend( + memo, + { + [fieldName]: fieldWithHooks( + { + fieldName, + fieldBehaviorScope: "proc:filterBy", + isPgConnectionConditionInputField: true, + pgFieldSource, + }, + { + description: build.wrapDescription( + `Checks for equality with the object’s \`${fieldName}\` field.`, + "field", + ), + type, + applyPlan: EXPORTABLE( + (pgFieldSource, sql) => + function plan( + $condition: PgConditionStep>, + val, + ) { + if (typeof pgFieldSource.from !== "function") { + throw new Error( + "Invalid computed attribute 'from'", + ); + } + const expression = sql`${pgFieldSource.from({ + placeholder: $condition.alias, + })}`; + if (val.getRaw().evalIs(null)) { + $condition.where(sql`${expression} is null`); + } else { + $condition.where( + sql`${expression} = ${$condition.placeholder( + val.get(), + pgFieldSource.codec, + )}`, + ); + } + }, + [pgFieldSource, sql], + ), + }, + ), + }, + `Adding computed attribute condition argument for ${pgCodec.name}`, + ); + return memo; + }, + Object.create(null), + ), `Adding computed attribute filterable functions to condition for '${pgCodec.name}'`, ); }, diff --git a/graphile-build/graphile-build-pg/src/plugins/PgConnectionArgOrderByPlugin.ts b/graphile-build/graphile-build-pg/src/plugins/PgConnectionArgOrderByPlugin.ts index f633710bf8..2e23264c49 100644 --- a/graphile-build/graphile-build-pg/src/plugins/PgConnectionArgOrderByPlugin.ts +++ b/graphile-build/graphile-build-pg/src/plugins/PgConnectionArgOrderByPlugin.ts @@ -2,6 +2,7 @@ import "./PgTablesPlugin.js"; import "graphile-config"; import type { + DefaultPgCodec, PgCodec, PgSelectParsedCursorStep, PgSelectSingleStep, @@ -25,7 +26,7 @@ declare global { orderByType(this: Inflection, typeName: string): string; } interface ScopeEnum { - pgCodec?: PgCodec; + pgCodec?: DefaultPgCodec; isPgRowSortEnum?: boolean; } } diff --git a/graphile-build/graphile-build-pg/src/plugins/PgCustomTypeFieldPlugin.ts b/graphile-build/graphile-build-pg/src/plugins/PgCustomTypeFieldPlugin.ts index 1f5a874040..870d5402a5 100644 --- a/graphile-build/graphile-build-pg/src/plugins/PgCustomTypeFieldPlugin.ts +++ b/graphile-build/graphile-build-pg/src/plugins/PgCustomTypeFieldPlugin.ts @@ -6,6 +6,13 @@ import "./PgProceduresPlugin.js"; import "graphile-config"; import type { + DefaultPgCodec, + DefaultPgDeleteSingleStep, + DefaultPgResource, + DefaultPgResourceParameter, + DefaultPgSelectSingleStep, + DefaultPgSelectStep, + DefaultPgUpdateSingleStep, PgClassSingleStep, PgCodec, PgDeleteSingleStep, @@ -57,8 +64,8 @@ declare global { namespace GraphileBuild { interface Build { pgGetArgDetailsFromParameters( - resource: PgResource, - parameters?: readonly PgResourceParameter[], + resource: DefaultPgResource, + parameters?: readonly DefaultPgResourceParameter[], ): { makeFieldArgs(): { [graphqlArgName: string]: { @@ -70,13 +77,13 @@ declare global { argDetails: Array<{ graphqlArgName: string; postgresArgName: string | null; - pgCodec: PgCodec; + pgCodec: DefaultPgCodec; inputType: GraphQLInputType; required: boolean; }>; makeExpression(opts: { $placeholderable: { - placeholder($step: ExecutableStep, codec: PgCodec): SQL; + placeholder($step: ExecutableStep, codec: DefaultPgCodec): SQL; }; resource: PgResource; fieldArgs: FieldArgs; @@ -87,15 +94,15 @@ declare global { } interface InflectionCustomFieldProcedureDetails { - resource: PgResource; + resource: DefaultPgResource; } interface InflectionCustomFieldArgumentDetails { - resource: PgResource; - param: PgResourceParameter; + resource: DefaultPgResource; + param: DefaultPgResourceParameter; index: number; } interface InflectionCustomFieldMutationResult { - resource: PgResource; + resource: DefaultPgResource; returnGraphQLTypeName: string; } @@ -172,7 +179,7 @@ declare global { } function shouldUseCustomConnection( - pgResource: PgResource, + pgResource: DefaultPgResource, ): boolean { const { codec } = pgResource; // 'setof ' functions should use a connection based on the function name, not a generic connection @@ -181,13 +188,9 @@ function shouldUseCustomConnection( return setOrArray && scalarOrAnonymous; } -function defaultProcSourceBehavior( - s: PgResource, -): string { +function defaultProcSourceBehavior(s: DefaultPgResource): string { const behavior = []; - const firstParameter = ( - s as PgResource - ).parameters[0]; + const firstParameter = s.parameters[0]; if ( !s.isMutation && s.parameters && @@ -232,22 +235,19 @@ function defaultProcSourceBehavior( function hasRecord( $row: ExecutableStep, ): $row is - | PgSelectSingleStep + | DefaultPgSelectSingleStep | PgInsertSingleStep - | PgUpdateSingleStep - | PgDeleteSingleStep { + | DefaultPgUpdateSingleStep + | DefaultPgDeleteSingleStep { return "record" in $row && typeof ($row as any).record === "function"; } declare global { namespace GraphileBuild { interface Build { - [$$rootQuery]: Array>; - [$$rootMutation]: Array>; - [$$computed]: Map< - PgCodec, - Array> - >; + [$$rootQuery]: Array; + [$$rootMutation]: Array; + [$$computed]: Map>; } } } @@ -531,10 +531,8 @@ export const PgCustomTypeFieldPlugin: GraphileConfig.Plugin = { step = constant(null); } } else if (fetcher) { - step = ( - fetcher( - args.get([...path, graphqlArgName]), - ) as PgSelectSingleStep + step = fetcher( + args.get([...path, graphqlArgName]), ).record(); } else { step = args.get([...path, graphqlArgName]); @@ -632,13 +630,7 @@ export const PgCustomTypeFieldPlugin: GraphileConfig.Plugin = { shouldUseCustomConnection(someSource); if (isFunctionSourceRequiringConnection) { - const resource = someSource as PgResource< - any, - any, - any, - readonly PgResourceParameter[], - any - >; + const resource = someSource; const connectionTypeName = resource.codec.attributes ? inflection.recordFunctionConnectionType({ resource, @@ -694,13 +686,7 @@ export const PgCustomTypeFieldPlugin: GraphileConfig.Plugin = { build.behavior.pgResourceMatches(someSource, "mutationField"); // Add payload type for mutation functions if (isMutationProcSource) { - const resource = someSource as PgResource< - any, - any, - any, - readonly PgResourceParameter[], - any - >; + const resource = someSource; build.recoverable(null, () => { const inputTypeName = inflection.customMutationInput({ resource, @@ -913,11 +899,10 @@ export const PgCustomTypeFieldPlugin: GraphileConfig.Plugin = { (memo, resource) => build.recoverable(memo, () => { // "Computed attributes" skip a parameter - const remainingParameters = ( + const remainingParameters = isRootMutation || isRootQuery ? resource.parameters - : resource.parameters.slice(1) - ) as PgResourceParameter[]; + : resource.parameters.slice(1); const { makeArgs, makeFieldArgs } = pgGetArgDetailsFromParameters( resource, @@ -963,7 +948,7 @@ export const PgCustomTypeFieldPlugin: GraphileConfig.Plugin = { ($in, args, _info) => { if (!hasRecord($in)) { throw new Error( - `Invalid plan, exepcted 'PgSelectSingleStep', 'PgInsertSingleStep', 'PgUpdateSingleStep' or 'PgDeleteSingleStep', but found ${$in}`, + `Invalid plan, expected 'PgSelectSingleStep', 'PgInsertSingleStep', 'PgUpdateSingleStep' or 'PgDeleteSingleStep', but found ${$in}`, ); } const extraSelectArgs = makeArgs(args); @@ -1201,7 +1186,7 @@ export const PgCustomTypeFieldPlugin: GraphileConfig.Plugin = { $parent, args, info, - ) as PgSelectStep; + ) as DefaultPgSelectStep; return connection( $select, ($item) => $item, @@ -1287,10 +1272,9 @@ export const PgCustomTypeFieldPlugin: GraphileConfig.Plugin = { function getFunctionSourceReturnGraphQLType( build: GraphileBuild.Build, - resource: PgResource, + resource: DefaultPgResource, ): GraphQLOutputType | null { - const resourceInnerCodec: PgCodec = - resource.codec.arrayOfCodec ?? resource.codec; + const resourceInnerCodec = resource.codec.arrayOfCodec ?? resource.codec; if (!resourceInnerCodec) { return null; } diff --git a/graphile-build/graphile-build-pg/src/plugins/PgFirstLastBeforeAfterArgsPlugin.ts b/graphile-build/graphile-build-pg/src/plugins/PgFirstLastBeforeAfterArgsPlugin.ts index 75e68ec884..c67f71f2ed 100644 --- a/graphile-build/graphile-build-pg/src/plugins/PgFirstLastBeforeAfterArgsPlugin.ts +++ b/graphile-build/graphile-build-pg/src/plugins/PgFirstLastBeforeAfterArgsPlugin.ts @@ -2,9 +2,9 @@ import "./PgTablesPlugin.js"; import "graphile-config"; import type { + DefaultPgSelectSingleStep, + DefaultPgSelectStep, PgSelectParsedCursorStep, - PgSelectSingleStep, - PgSelectStep, } from "@dataplan/pg"; import type { ConnectionStep, GrafastFieldConfigArgumentMap } from "grafast"; import { EXPORTABLE } from "graphile-build"; @@ -93,9 +93,9 @@ function commonFn( function plan( _: any, $connection: ConnectionStep< - PgSelectSingleStep, + DefaultPgSelectSingleStep, PgSelectParsedCursorStep, - PgSelectStep + DefaultPgSelectStep >, arg, ) { @@ -118,9 +118,9 @@ function commonFn( function plan( _: any, $connection: ConnectionStep< - PgSelectSingleStep, + DefaultPgSelectSingleStep, PgSelectParsedCursorStep, - PgSelectStep + DefaultPgSelectStep >, val, ) { @@ -145,9 +145,9 @@ function commonFn( function plan( _: any, $connection: ConnectionStep< - PgSelectSingleStep, + DefaultPgSelectSingleStep, PgSelectParsedCursorStep, - PgSelectStep + DefaultPgSelectStep >, val, ) { @@ -170,9 +170,9 @@ function commonFn( function plan( _: any, $connection: ConnectionStep< - PgSelectSingleStep, + DefaultPgSelectSingleStep, PgSelectParsedCursorStep, - PgSelectStep + DefaultPgSelectStep >, val, ) { @@ -193,9 +193,9 @@ function commonFn( function plan( _: any, $connection: ConnectionStep< - PgSelectSingleStep, + DefaultPgSelectSingleStep, PgSelectParsedCursorStep, - PgSelectStep + DefaultPgSelectStep >, val, ) { diff --git a/graphile-build/graphile-build-pg/src/plugins/PgInterfaceModeUnionAllRowsPlugin.ts b/graphile-build/graphile-build-pg/src/plugins/PgInterfaceModeUnionAllRowsPlugin.ts index 461369e44f..f677aee712 100644 --- a/graphile-build/graphile-build-pg/src/plugins/PgInterfaceModeUnionAllRowsPlugin.ts +++ b/graphile-build/graphile-build-pg/src/plugins/PgInterfaceModeUnionAllRowsPlugin.ts @@ -1,9 +1,9 @@ import "graphile-config"; import type { + DefaultPgCodec, + DefaultPgResource, PgCodec, - PgRegistry, - PgResource, PgUnionAllStepConfigAttributes, PgUnionAllStepMember, } from "@dataplan/pg"; @@ -21,7 +21,7 @@ declare global { * The base inflector used by allInterfaceModeUnionRowsConnection and * allInterfaceModeUnionRowsList. */ - _allInterfaceModeUnionRows(this: Inflection, codec: PgCodec): string; + _allInterfaceModeUnionRows(this: Inflection, codec: DefaultPgCodec): string; /** * The field name for a Cursor Connection field that returns all rows @@ -29,14 +29,14 @@ declare global { */ allInterfaceModeUnionRowsConnection( this: Inflection, - codec: PgCodec, + codec: DefaultPgCodec, ): string; /** * The field name for a List field that returns all rows from the given * `@interface mode:union` codec. */ - allInterfaceModeUnionRowsList(this: Inflection, codec: PgCodec): string; + allInterfaceModeUnionRowsList(this: Inflection, codec: DefaultPgCodec): string; } } } @@ -90,11 +90,11 @@ export const PgInterfaceModeUnionAllRowsPlugin: GraphileConfig.Plugin = { return fields; } - const pgRegistry = input.pgRegistry as PgRegistry; + const pgRegistry = input.pgRegistry; const resourcesByPolymorphicTypeName: { [polymorphicTypeName: string]: { - resources: PgResource[]; + resources: DefaultPgResource[]; type: "union" | "interface"; }; } = Object.create(null); @@ -135,7 +135,7 @@ export const PgInterfaceModeUnionAllRowsPlugin: GraphileConfig.Plugin = { for (const interfaceName of interfaces) { if (!resourcesByPolymorphicTypeName[interfaceName]) { resourcesByPolymorphicTypeName[interfaceName] = { - resources: [resource as PgResource], + resources: [resource], type: "interface", }; } else { @@ -146,15 +146,16 @@ export const PgInterfaceModeUnionAllRowsPlugin: GraphileConfig.Plugin = { throw new Error(`Inconsistent polymorphism`); } resourcesByPolymorphicTypeName[interfaceName].resources.push( - resource as PgResource, + resource, ); } } } } - const interfaceCodecs: { [polymorphicTypeName: string]: PgCodec } = - Object.create(null); + const interfaceCodecs: { + [polymorphicTypeName: string]: DefaultPgCodec; + } = Object.create(null); for (const codec of Object.values(pgRegistry.pgCodecs)) { if (!codec.polymorphism) continue; if (codec.polymorphism.mode !== "union") continue; @@ -216,7 +217,7 @@ export const PgInterfaceModeUnionAllRowsPlugin: GraphileConfig.Plugin = { if (!interfaceCodec.attributes) return; const attributes: PgUnionAllStepConfigAttributes = interfaceCodec.attributes; - const resourceByTypeName: Record = + const resourceByTypeName: Record = Object.create(null); const members: PgUnionAllStepMember[] = []; for (const resource of spec.resources) { diff --git a/graphile-build/graphile-build-pg/src/plugins/PgJWTPlugin.ts b/graphile-build/graphile-build-pg/src/plugins/PgJWTPlugin.ts index b0e55b8f6f..6f32f8fe02 100644 --- a/graphile-build/graphile-build-pg/src/plugins/PgJWTPlugin.ts +++ b/graphile-build/graphile-build-pg/src/plugins/PgJWTPlugin.ts @@ -1,6 +1,11 @@ import "graphile-config"; -import type { PgCodec, PgSelectSingleStep } from "@dataplan/pg"; +import type { + DefaultPgCodec, + DefaultPgSelectSingleStep, + PgCodec, + PgSelectSingleStep, +} from "@dataplan/pg"; import { EXPORTABLE, gatherConfig } from "graphile-build"; import type { Secret, SignOptions } from "jsonwebtoken"; import { sign as signJwt } from "jsonwebtoken"; @@ -35,7 +40,7 @@ declare global { interface ScopeScalar { isPgJwtType?: boolean; - pgCodec?: PgCodec; + pgCodec?: DefaultPgCodec; } } } @@ -185,7 +190,7 @@ export const PgJWTPlugin: GraphileConfig.Plugin = { plan: EXPORTABLE( () => function plan($in) { - const $record = $in as PgSelectSingleStep; + const $record = $in as DefaultPgSelectSingleStep; return $record.record(); }, [], diff --git a/graphile-build/graphile-build-pg/src/plugins/PgMutationPayloadEdgePlugin.ts b/graphile-build/graphile-build-pg/src/plugins/PgMutationPayloadEdgePlugin.ts index 68a8caf9bd..7f9950d8ef 100644 --- a/graphile-build/graphile-build-pg/src/plugins/PgMutationPayloadEdgePlugin.ts +++ b/graphile-build/graphile-build-pg/src/plugins/PgMutationPayloadEdgePlugin.ts @@ -1,10 +1,6 @@ import "graphile-config"; -import type { - PgClassSingleStep, - PgCodecWithAttributes, - PgResourceUnique, -} from "@dataplan/pg"; +import type { DefaultPgCodec, PgClassSingleStep } from "@dataplan/pg"; import { PgDeleteSingleStep, pgSelectFromRecord } from "@dataplan/pg"; import type { FieldArgs, FieldInfo, ObjectStep } from "grafast"; import { connection, constant, EdgeStep, first } from "grafast"; @@ -18,7 +14,7 @@ import { applyOrderToPlan } from "./PgConnectionArgOrderByPlugin.js"; declare global { namespace GraphileBuild { interface Inflection { - tableEdgeField(this: Inflection, codec: PgCodecWithAttributes): string; + tableEdgeField(this: Inflection, codec: DefaultPgCodec): string; } interface ScopeObjectFieldsField { @@ -84,9 +80,7 @@ export const PgMutationPayloadEdgePlugin: GraphileConfig.Plugin = { return fields; } - const pk = (resource.uniques as PgResourceUnique[])?.find( - (u) => u.isPrimary, - ); + const pk = resource.uniques?.find((u) => u.isPrimary); if (!pk) { return fields; } diff --git a/graphile-build/graphile-build-pg/src/plugins/PgMutationUpdateDeletePlugin.ts b/graphile-build/graphile-build-pg/src/plugins/PgMutationUpdateDeletePlugin.ts index 98fe243399..6f13f71e5e 100644 --- a/graphile-build/graphile-build-pg/src/plugins/PgMutationUpdateDeletePlugin.ts +++ b/graphile-build/graphile-build-pg/src/plugins/PgMutationUpdateDeletePlugin.ts @@ -1,6 +1,10 @@ import "graphile-config"; import type { + DefaultPgDeleteSingleStep, + DefaultPgResource, + DefaultPgResourceUnique, + DefaultPgUpdateSingleStep, PgClassSingleStep, PgCodecWithAttributes, PgDeleteSingleStep, @@ -31,7 +35,7 @@ declare global { interface ScopeObject { isPgUpdatePayloadType?: boolean; isPgDeletePayloadType?: boolean; - pgTypeResource?: PgResource; + pgTypeResource?: DefaultPgResource; } interface ScopeObjectFieldsField { @@ -45,88 +49,88 @@ declare global { isPgDeleteInputType?: boolean; isPgDeleteByKeysInputType?: boolean; isPgDeleteNodeInputType?: boolean; - pgResource?: PgResource; - pgResourceUnique?: PgResourceUnique; + pgResource?: DefaultPgResource; + pgResourceUnique?: DefaultPgResourceUnique; } interface Inflection { updatePayloadType( this: Inflection, details: { - resource: PgResource; + resource: DefaultPgResource; }, ): string; deletePayloadType( this: Inflection, details: { - resource: PgResource; + resource: DefaultPgResource; }, ): string; updateNodeField( this: Inflection, details: { - resource: PgResource; - unique: PgResourceUnique; + resource: DefaultPgResource; + unique: DefaultPgResourceUnique; }, ): string; updateNodeInputType( this: Inflection, details: { - resource: PgResource; - unique: PgResourceUnique; + resource: DefaultPgResource; + unique: DefaultPgResourceUnique; }, ): string; deletedNodeId( this: Inflection, details: { - resource: PgResource; + resource: DefaultPgResource; }, ): string; deleteNodeField( this: Inflection, details: { - resource: PgResource; - unique: PgResourceUnique; + resource: DefaultPgResource; + unique: DefaultPgResourceUnique; }, ): string; deleteNodeInputType( this: Inflection, details: { - resource: PgResource; - unique: PgResourceUnique; + resource: DefaultPgResource; + unique: DefaultPgResourceUnique; }, ): string; updateByKeysField( this: Inflection, details: { - resource: PgResource; - unique: PgResourceUnique; + resource: DefaultPgResource; + unique: DefaultPgResourceUnique; }, ): string; updateByKeysInputType( this: Inflection, details: { - resource: PgResource; - unique: PgResourceUnique; + resource: DefaultPgResource; + unique: DefaultPgResourceUnique; }, ): string; deleteByKeysField( this: Inflection, details: { - resource: PgResource; - unique: PgResourceUnique; + resource: DefaultPgResource; + unique: DefaultPgResourceUnique; }, ): string; deleteByKeysInputType( this: Inflection, details: { - resource: PgResource; - unique: PgResourceUnique; + resource: DefaultPgResource; + unique: DefaultPgResourceUnique; }, ): string; @@ -247,7 +251,7 @@ export const PgMutationUpdateDeletePlugin: GraphileConfig.Plugin = { } = build; const process = ( - resource: PgResource, + resource: DefaultPgResource, mode: "resource:update" | "resource:delete", ) => { const modeText = mode === "resource:update" ? "update" : "delete"; @@ -337,8 +341,8 @@ export const PgMutationUpdateDeletePlugin: GraphileConfig.Plugin = { function plan( $object: ObjectStep<{ result: - | PgUpdateSingleStep - | PgDeleteSingleStep; + | DefaultPgUpdateSingleStep + | DefaultPgDeleteSingleStep; }>, ) { return $object.get("result"); @@ -497,7 +501,7 @@ export const PgMutationUpdateDeletePlugin: GraphileConfig.Plugin = { : (unique.attributes as string[]).reduce( (memo, attributeName) => { const attribute = - resource.codec.attributes[attributeName]; + resource.codec.attributes![attributeName]; memo[ inflection.attribute({ attributeName, @@ -531,7 +535,7 @@ export const PgMutationUpdateDeletePlugin: GraphileConfig.Plugin = { () => function plan( $object: ObjectStep<{ - result: PgUpdateSingleStep; + result: DefaultPgUpdateSingleStep; }>, ) { const $record = @@ -555,30 +559,12 @@ export const PgMutationUpdateDeletePlugin: GraphileConfig.Plugin = { } }; - const allResources = Object.values( - build.input.pgRegistry.pgResources, - ) as PgResource[]; - const updatableResources = allResources.filter( - ( - resource, - ): resource is PgResource< - any, - PgCodecWithAttributes, - any, - any, - any - > => isUpdatable(build, resource), + const allResources = Object.values(build.input.pgRegistry.pgResources); + const updatableResources = allResources.filter((resource) => + isUpdatable(build, resource), ); - const deletableResources = allResources.filter( - ( - resource, - ): resource is PgResource< - any, - PgCodecWithAttributes, - any, - any, - any - > => isDeletable(build, resource), + const deletableResources = allResources.filter((resource) => + isDeletable(build, resource), ); updatableResources.forEach((resource) => { @@ -764,8 +750,8 @@ export const PgMutationUpdateDeletePlugin: GraphileConfig.Plugin = { _: any, $object: ObjectStep<{ result: - | PgUpdateSingleStep - | PgDeleteSingleStep; + | DefaultPgUpdateSingleStep + | DefaultPgDeleteSingleStep; }>, ) { return $object; @@ -892,11 +878,11 @@ return (_$root, args) => { function getSpecs( build: GraphileBuild.Build, - resource: PgResource, + resource: DefaultPgResource, mode: "resource:update" | "resource:delete", ) { const primaryUnique = resource.uniques.find( - (u: PgResourceUnique) => u.isPrimary, + (u: DefaultPgResourceUnique) => u.isPrimary, ); const constraintMode = `constraint:${mode}`; const specs = [ @@ -906,13 +892,13 @@ function getSpecs( ? [{ unique: primaryUnique, uniqueMode: "node" }] : []), ...resource.uniques - .filter((unique: PgResourceUnique) => { + .filter((unique: DefaultPgResourceUnique) => { return build.behavior.pgResourceUniqueMatches( [resource, unique], constraintMode, ); }) - .map((unique: PgResourceUnique) => ({ + .map((unique: DefaultPgResourceUnique) => ({ unique, uniqueMode: "keys", })), diff --git a/graphile-build/graphile-build-pg/src/plugins/PgNodeIdAttributesPlugin.ts b/graphile-build/graphile-build-pg/src/plugins/PgNodeIdAttributesPlugin.ts index e6976b3ede..631bebf136 100644 --- a/graphile-build/graphile-build-pg/src/plugins/PgNodeIdAttributesPlugin.ts +++ b/graphile-build/graphile-build-pg/src/plugins/PgNodeIdAttributesPlugin.ts @@ -3,10 +3,9 @@ import "../interfaces.js"; import "graphile-config"; import type { - PgCodecRelation, - PgCodecWithAttributes, + DefaultPgCodec, + DefaultPgRegistry, PgConditionStep, - PgRegistry, PgSelectStep, } from "@dataplan/pg"; import type { SetterStep } from "grafast"; @@ -24,8 +23,8 @@ declare global { nodeIdAttribute( this: Inflection, details: { - registry: PgRegistry; - codec: PgCodecWithAttributes; + registry: DefaultPgRegistry; + codec: DefaultPgCodec; relationName: string; }, ): string; @@ -82,13 +81,10 @@ export const PgNodeIdAttributesPlugin: GraphileConfig.Plugin = { ) { return fields; } - const pgCodec = rawPgCodec as PgCodecWithAttributes; + const pgCodec = rawPgCodec; - const relationEntries = ( - Object.entries(pgRegistry.pgRelations[pgCodec.name] ?? {}) as [ - string, - PgCodecRelation, - ][] + const relationEntries = Object.entries( + pgRegistry.pgRelations[pgCodec.name] ?? {}, ).filter( ([_name, relation]) => !relation.isReferencee && relation.isUnique, ); @@ -134,14 +130,14 @@ export const PgNodeIdAttributesPlugin: GraphileConfig.Plugin = { relationName, }); const attributes = relation.localAttributes.map( - (name) => pgCodec.attributes[name], + (name) => pgCodec.attributes![name], ); const anAttributeIsNotNull = attributes.some( (attr) => attr.notNull || attr.extensions?.tags?.notNull, ); const { localAttributes, remoteAttributes } = relation; const localAttributeCodecs = localAttributes.map( - (name) => pgCodec.attributes[name].codec, + (name) => pgCodec.attributes![name].codec, ); return extend( memo, diff --git a/graphile-build/graphile-build-pg/src/plugins/PgOrderAllAttributesPlugin.ts b/graphile-build/graphile-build-pg/src/plugins/PgOrderAllAttributesPlugin.ts index d2699054e4..c9c5a3d2fa 100644 --- a/graphile-build/graphile-build-pg/src/plugins/PgOrderAllAttributesPlugin.ts +++ b/graphile-build/graphile-build-pg/src/plugins/PgOrderAllAttributesPlugin.ts @@ -1,7 +1,7 @@ import "./PgTablesPlugin.js"; import "graphile-config"; -import type { PgCodecAttribute, PgCodecWithAttributes } from "@dataplan/pg"; +import type { DefaultPgCodec, DefaultPgCodecAttribute } from "@dataplan/pg"; import { PgSelectStep, PgUnionAllStep } from "@dataplan/pg"; import type { ExecutableStep, ModifierStep } from "grafast"; import type { GraphQLEnumValueConfigMap } from "grafast/graphql"; @@ -15,9 +15,9 @@ declare global { orderByAttributeEnum( this: Inflection, details: { - codec: PgCodecWithAttributes; + codec: DefaultPgCodec; attributeName: string; - attribute: PgCodecAttribute; + attribute: DefaultPgCodecAttribute; variant: "asc" | "desc" | "asc_nulls_last" | "desc_nulls_last"; }, ): string; @@ -59,8 +59,8 @@ export const PgOrderAllAttributesPlugin: GraphileConfig.Plugin = { ) { return values; } - const pgCodec = rawPgCodec as PgCodecWithAttributes; - const allAttributes = pgCodec.attributes; + const pgCodec = rawPgCodec; + const allAttributes = pgCodec.attributes!; const allowedAttributes = pgCodec.polymorphism?.mode === "single" ? [ diff --git a/graphile-build/graphile-build-pg/src/plugins/PgOrderByPrimaryKeyPlugin.ts b/graphile-build/graphile-build-pg/src/plugins/PgOrderByPrimaryKeyPlugin.ts index 42da22c064..84b346144e 100644 --- a/graphile-build/graphile-build-pg/src/plugins/PgOrderByPrimaryKeyPlugin.ts +++ b/graphile-build/graphile-build-pg/src/plugins/PgOrderByPrimaryKeyPlugin.ts @@ -1,11 +1,7 @@ import "./PgTablesPlugin.js"; import "graphile-config"; -import type { - PgCodecWithAttributes, - PgResourceUnique, - PgSelectStep, -} from "@dataplan/pg"; +import type { DefaultPgSelectStep } from "@dataplan/pg"; import { EXPORTABLE } from "graphile-build"; import { version } from "../version.js"; @@ -41,16 +37,14 @@ export const PgOrderByPrimaryKeyPlugin: GraphileConfig.Plugin = { return values; } - const pgCodec = rawPgCodec as PgCodecWithAttributes; + const pgCodec = rawPgCodec; const resource = build.pgTableResource(pgCodec); if (!resource) { return values; } - const primaryKey = (resource.uniques as PgResourceUnique[]).find( - (unique) => unique.isPrimary, - ); + const primaryKey = resource.uniques.find((unique) => unique.isPrimary); if (!primaryKey) { return values; } @@ -64,9 +58,9 @@ export const PgOrderByPrimaryKeyPlugin: GraphileConfig.Plugin = { grafast: { applyPlan: EXPORTABLE( (pgCodec, pgOrderByNullsLast, primaryKeyAttributes, sql) => - (step: PgSelectStep) => { + (step: DefaultPgSelectStep) => { primaryKeyAttributes.forEach((attributeName) => { - const attribute = pgCodec.attributes[attributeName]; + const attribute = pgCodec.attributes![attributeName]; step.orderBy({ codec: attribute.codec, fragment: sql`${step.alias}.${sql.identifier( @@ -92,9 +86,9 @@ export const PgOrderByPrimaryKeyPlugin: GraphileConfig.Plugin = { grafast: { applyPlan: EXPORTABLE( (pgCodec, pgOrderByNullsLast, primaryKeyAttributes, sql) => - (step: PgSelectStep) => { + (step: DefaultPgSelectStep) => { primaryKeyAttributes.forEach((attributeName) => { - const attribute = pgCodec.attributes[attributeName]; + const attribute = pgCodec.attributes![attributeName]; step.orderBy({ codec: attribute.codec, fragment: sql`${step.alias}.${sql.identifier( diff --git a/graphile-build/graphile-build-pg/src/plugins/PgOrderCustomFieldsPlugin.ts b/graphile-build/graphile-build-pg/src/plugins/PgOrderCustomFieldsPlugin.ts index 18126e7ab0..7a4ca67b08 100644 --- a/graphile-build/graphile-build-pg/src/plugins/PgOrderCustomFieldsPlugin.ts +++ b/graphile-build/graphile-build-pg/src/plugins/PgOrderCustomFieldsPlugin.ts @@ -2,9 +2,8 @@ import "./PgTablesPlugin.js"; import "graphile-config"; import type { - PgResource, - PgResourceParameter, - PgSelectStep, + DefaultPgResource, + DefaultPgSelectStep, } from "@dataplan/pg"; import { EXPORTABLE } from "graphile-build"; @@ -17,13 +16,7 @@ declare global { computedAttributeOrder( this: Inflection, details: { - resource: PgResource< - any, - any, - any, - readonly PgResourceParameter[], - any - >; + resource: DefaultPgResource; variant: "asc" | "desc" | "asc_nulls_last" | "desc_nulls_last"; }, ): string; @@ -90,13 +83,7 @@ export const PgOrderCustomFieldsPlugin: GraphileConfig.Plugin = { functionSources.reduce((memo, pgFieldSource) => { for (const ascDesc of ["asc" as const, "desc" as const]) { const valueName = inflection.computedAttributeOrder({ - resource: pgFieldSource as PgResource< - any, - any, - any, - readonly PgResourceParameter[], - any - >, + resource: pgFieldSource, variant: ascDesc, }); @@ -108,7 +95,7 @@ export const PgOrderCustomFieldsPlugin: GraphileConfig.Plugin = { grafast: { applyPlan: EXPORTABLE( (ascDesc, pgFieldSource, sql) => - (step: PgSelectStep) => { + (step: DefaultPgSelectStep) => { if (typeof pgFieldSource.from !== "function") { throw new Error( "Invalid computed attribute 'from'", diff --git a/graphile-build/graphile-build-pg/src/plugins/PgPolymorphismPlugin.ts b/graphile-build/graphile-build-pg/src/plugins/PgPolymorphismPlugin.ts index 6ed9889ac9..9dcbd0deef 100644 --- a/graphile-build/graphile-build-pg/src/plugins/PgPolymorphismPlugin.ts +++ b/graphile-build/graphile-build-pg/src/plugins/PgPolymorphismPlugin.ts @@ -5,7 +5,9 @@ import "./PgRelationsPlugin.js"; import "./PgTablesPlugin.js"; import type { - PgCodec, + DefaultPgCodec, + DefaultPgRelation, + DefaultPgSelectSingleStep, PgCodecExtensions, PgCodecPolymorphism, PgCodecPolymorphismRelational, @@ -14,14 +16,7 @@ import type { PgCodecPolymorphismSingleTypeAttributeSpec, PgCodecPolymorphismSingleTypeSpec, PgCodecRef, - PgCodecRelation, - PgCodecWithAttributes, PgRefDefinition, - PgRegistry, - PgResource, - PgResourceOptions, - PgResourceUnique, - PgSelectSingleStep, } from "@dataplan/pg"; import { assertPgClassSingleStep } from "@dataplan/pg"; import type { ExecutableStep, ListStep, NodeIdHandler } from "grafast"; @@ -61,14 +56,14 @@ declare global { } namespace GraphileBuild { interface Build { - nodeIdSpecForCodec(codec: PgCodec): + nodeIdSpecForCodec(codec: DefaultPgCodec): | (($nodeId: ExecutableStep) => { [key: string]: ExecutableStep; }) | null; } interface ScopeInterface { - pgCodec?: PgCodec; + pgCodec?: DefaultPgCodec; isPgPolymorphicTableType?: boolean; pgPolymorphism?: PgCodecPolymorphism; } @@ -335,9 +330,7 @@ export const PgPolymorphismPlugin: GraphileConfig.Plugin = { async pgRegistry_PgRegistryBuilder_finalize(info, event) { const { registryBuilder } = event; const registryConfig = registryBuilder.getRegistryConfig(); - for (const resource of Object.values( - registryConfig.pgResources, - ) as PgResourceOptions[]) { + for (const resource of Object.values(registryConfig.pgResources)) { if (resource.parameters || !resource.codec.attributes) { continue; } @@ -359,7 +352,7 @@ export const PgPolymorphismPlugin: GraphileConfig.Plugin = { continue; } - const poly = (resource.codec as PgCodec).polymorphism; + const poly = resource.codec.polymorphism; if (poly?.mode === "relational") { // Copy common attributes to implementations for (const spec of Object.values(poly.types)) { @@ -438,6 +431,7 @@ export const PgPolymorphismPlugin: GraphileConfig.Plugin = { sharedRelationName; } else { otherCodec.attributes[colName] = { + name: colName, codec: colSpec.codec, notNull: colSpec.notNull, hasDefault: colSpec.hasDefault, @@ -463,13 +457,7 @@ export const PgPolymorphismPlugin: GraphileConfig.Plugin = { if (rawResource.parameters || !rawResource.codec.attributes) { continue; } - const resource = rawResource as PgResource< - string, - PgCodecWithAttributes, - any, - undefined, - PgRegistry - >; + const resource = rawResource; if (!resource.extensions?.pg) { continue; } @@ -488,11 +476,8 @@ export const PgPolymorphismPlugin: GraphileConfig.Plugin = { continue; } - const relations = registry.pgRelations[resource.codec.name] as Record< - string, - PgCodecRelation - >; - const poly = (resource.codec as PgCodec).polymorphism; + const relations = registry.pgRelations[resource.codec.name]; + const poly = resource.codec.polymorphism; if (poly?.mode === "relational") { // Copy common attributes to implementations for (const spec of Object.values(poly.types)) { @@ -738,7 +723,7 @@ export const PgPolymorphismPlugin: GraphileConfig.Plugin = { !resource.isUnique && !resource.isVirtual ) { - if ((resource.codec as PgCodec).polymorphism) { + if (resource.codec.polymorphism) { // This is a polymorphic type newBehavior.push( "-resource:insert -resource:update -resource:delete", @@ -747,11 +732,9 @@ export const PgPolymorphismPlugin: GraphileConfig.Plugin = { const resourceTypeName = build.inflection.tableType( resource.codec, ); - const relations: Record = + const relations: Record = resource.getRelations(); - const pk = ( - resource.uniques as PgResourceUnique[] | undefined - )?.find((u) => u.isPrimary); + const pk = resource.uniques?.find((u) => u.isPrimary); if (pk) { const pkAttributes = pk.attributes; const pkRelations = Object.values(relations).filter((r) => @@ -808,9 +791,9 @@ export const PgPolymorphismPlugin: GraphileConfig.Plugin = { if (!handler) { return null; } - const pk = ( - relation.remoteResource.uniques as PgResourceUnique[] - ).find((u) => u.isPrimary); + const pk = relation.remoteResource.uniques.find( + (u) => u.isPrimary, + ); if (!pk) { return null; } @@ -987,7 +970,7 @@ export const PgPolymorphismPlugin: GraphileConfig.Plugin = { setGraphQLTypeForPgCodec, grafast: { list, constant, access }, } = build; - const unionsToRegister = new Map(); + const unionsToRegister = new Map(); for (const codec of build.pgCodecMetaLookup.keys()) { if (!codec.attributes) { // Only apply to codecs that define attributes @@ -1048,13 +1031,9 @@ export const PgPolymorphismPlugin: GraphileConfig.Plugin = { }, nonNullNode: pgForbidSetofFunctionsToReturnNull, }); - const resource = build.pgTableResource( - codec as PgCodecWithAttributes, - ); + const resource = build.pgTableResource(codec); const primaryKey = resource - ? (resource.uniques as PgResourceUnique[]).find( - (u) => u.isPrimary === true, - ) + ? resource.uniques.find((u) => u.isPrimary === true) : undefined; const pk = primaryKey?.attributes; for (const [typeIdentifier, spec] of Object.entries( @@ -1136,7 +1115,7 @@ return function (list, constant) { ) : EXPORTABLE( (constant, list, pk, tableTypeName) => - ($record: PgSelectSingleStep) => { + ($record: DefaultPgSelectSingleStep) => { return list([ constant(tableTypeName, false), ...pk.map((attribute) => diff --git a/graphile-build/graphile-build-pg/src/plugins/PgProceduresPlugin.ts b/graphile-build/graphile-build-pg/src/plugins/PgProceduresPlugin.ts index 0d51d019da..5153009794 100644 --- a/graphile-build/graphile-build-pg/src/plugins/PgProceduresPlugin.ts +++ b/graphile-build/graphile-build-pg/src/plugins/PgProceduresPlugin.ts @@ -3,12 +3,12 @@ // (e.g. they can be relations to other tables), so we've renamed them. import type { - PgCodec, - PgCodecAttributes, - PgFunctionResourceOptions, + DefaultPgCodec, + DefaultPgCodecAttributesRecord, + DefaultPgFunctionResourceOptions, + DefaultPgResourceOptions, + DefaultPgResourceParameter, PgResourceExtensions, - PgResourceOptions, - PgResourceParameter, PgSelectArgumentDigest, } from "@dataplan/pg"; import { @@ -64,7 +64,7 @@ declare global { getResourceOptions( serviceName: string, pgProc: PgProc, - ): Promise; + ): Promise; }; } @@ -72,15 +72,18 @@ declare global { pgProcedures_functionResourceOptions(event: { serviceName: string; pgProc: PgProc; - baseResourceOptions: Pick & - Partial>; - functionResourceOptions: PgFunctionResourceOptions; + baseResourceOptions: Pick< + DefaultPgResourceOptions, + "codec" | "executor" + > & + Partial>; + functionResourceOptions: DefaultPgFunctionResourceOptions; }): void | Promise; pgProcedures_PgResourceOptions(event: { serviceName: string; pgProc: PgProc; - resourceOptions: PgResourceOptions; + resourceOptions: DefaultPgResourceOptions; }): void | Promise; } } @@ -89,7 +92,7 @@ declare global { interface State { resourceOptionsByPgProcByService: Map< string, - Map> + Map> >; } const EMPTY_OBJECT = Object.freeze({}); @@ -213,89 +216,92 @@ export const PgProceduresPlugin: GraphileConfig.Plugin = { const { tags: rawTags, description } = pgProc.getTagsAndDescription(); const tags = JSON.parse(JSON.stringify(rawTags)); - const makeCodecFromReturn = async (): Promise => { - // We're building a PgCodec to represent specifically the - // return type of this function. - - const numberOfArguments = allArgTypes.length ?? 0; - const attributes: PgCodecAttributes = Object.create(null); - for (let i = 0, l = numberOfArguments; i < l; i++) { - // i for IN arguments, o for OUT arguments, b for INOUT arguments, - // v for VARIADIC arguments, t for TABLE arguments - const argMode = (pgProc.proargmodes?.[i] ?? "i") as - | "i" - | "o" - | "b" - | "v" - | "t"; - - if (argMode === "o" || argMode === "b" || argMode === "t") { - const argType = allArgTypes[i]; - const trueArgName = pgProc.proargnames?.[i]; - const argName = trueArgName || `column${i + 1}`; - - const tag = tags[`arg${i}modifier`]; - const typeModifier = - typeof tag === "string" - ? /^[0-9]+$/.test(tag) - ? parseInt(tag, 10) - : tag - : undefined; - - // This argument exists on the record type output - // NOTE: we treat `OUT foo`, `INOUT foo` and - // `RETURNS TABLE (foo ...)` as the same. - const attributeCodec = - await info.helpers.pgCodecs.getCodecFromType( - serviceName, - argType, - typeModifier, - ); - if (!attributeCodec) { - console.warn( - `Could not make codec for '${debugProcName}' argument '${argName}' which has type ${argType} (${ - (await info.helpers.pgIntrospection.getType( - serviceName, - argType, - ))!.typname - }); skipping function`, - ); - return null; + const makeCodecFromReturn = + async (): Promise => { + // We're building a PgCodec to represent specifically the + // return type of this function. + + const numberOfArguments = allArgTypes.length ?? 0; + const attributes: DefaultPgCodecAttributesRecord = + Object.create(null); + for (let i = 0, l = numberOfArguments; i < l; i++) { + // i for IN arguments, o for OUT arguments, b for INOUT arguments, + // v for VARIADIC arguments, t for TABLE arguments + const argMode = (pgProc.proargmodes?.[i] ?? "i") as + | "i" + | "o" + | "b" + | "v" + | "t"; + + if (argMode === "o" || argMode === "b" || argMode === "t") { + const argType = allArgTypes[i]; + const trueArgName = pgProc.proargnames?.[i]; + const argName = trueArgName || `column${i + 1}`; + + const tag = tags[`arg${i}modifier`]; + const typeModifier = + typeof tag === "string" + ? /^[0-9]+$/.test(tag) + ? parseInt(tag, 10) + : tag + : undefined; + + // This argument exists on the record type output + // NOTE: we treat `OUT foo`, `INOUT foo` and + // `RETURNS TABLE (foo ...)` as the same. + const attributeCodec = + await info.helpers.pgCodecs.getCodecFromType( + serviceName, + argType, + typeModifier, + ); + if (!attributeCodec) { + console.warn( + `Could not make codec for '${debugProcName}' argument '${argName}' which has type ${argType} (${ + (await info.helpers.pgIntrospection.getType( + serviceName, + argType, + ))!.typname + }); skipping function`, + ); + return null; + } + attributes[argName] = { + name: argName, + notNull: false, + codec: attributeCodec, + extensions: { + argIndex: i, + argName: trueArgName, + }, + // ENHANCE: could use "param" smart tag in function to add extensions here? + }; } - attributes[argName] = { - notNull: false, - codec: attributeCodec, - extensions: { - argIndex: i, - argName: trueArgName, - }, - // ENHANCE: could use "param" smart tag in function to add extensions here? - }; } - } - const recordCodecName = - info.inflection.functionRecordReturnCodecName({ - pgProc, - serviceName, - }); - return EXPORTABLE( - (attributes, executor, recordCodec, recordCodecName, sql) => - recordCodec({ - name: recordCodecName, - identifier: sql`ANONYMOUS_TYPE_DO_NOT_REFERENCE`, - attributes, - description: undefined, - extensions: { - /* `The return type of our \`${name}\` ${ + const recordCodecName = + info.inflection.functionRecordReturnCodecName({ + pgProc, + serviceName, + }); + return EXPORTABLE( + (attributes, executor, recordCodec, recordCodecName, sql) => + recordCodec({ + name: recordCodecName, + identifier: sql`ANONYMOUS_TYPE_DO_NOT_REFERENCE`, + attributes, + description: undefined, + extensions: { + /* `The return type of our \`${name}\` ${ pgProc.provolatile === "v" ? "mutation" : "query" }.`, */ - }, - executor, - isAnonymous: true, - }), - [attributes, executor, recordCodec, recordCodecName, sql], - ); - }; + }, + executor, + isAnonymous: true, + }), + [attributes, executor, recordCodec, recordCodecName, sql], + ); + }; const returnCodec = needsPayloadCodecToBeGenerated ? await makeCodecFromReturn() @@ -311,7 +317,7 @@ export const PgProceduresPlugin: GraphileConfig.Plugin = { return null; } - const parameters: PgResourceParameter[] = []; + const parameters: DefaultPgResourceParameter[] = []; // const processedFirstInputArg = false; @@ -498,7 +504,7 @@ export const PgProceduresPlugin: GraphileConfig.Plugin = { return null; } - const options: PgFunctionResourceOptions = { + const options: DefaultPgFunctionResourceOptions = { name, identifier, from: fromCallback, @@ -535,7 +541,7 @@ export const PgProceduresPlugin: GraphileConfig.Plugin = { [finalResourceOptions, makePgResourceOptions], ); } else { - const options: PgResourceOptions = EXPORTABLE( + const options: DefaultPgResourceOptions = EXPORTABLE( ( description, executor, diff --git a/graphile-build/graphile-build-pg/src/plugins/PgRefsPlugin.ts b/graphile-build/graphile-build-pg/src/plugins/PgRefsPlugin.ts index 0ea9e513bc..22d7ed6aca 100644 --- a/graphile-build/graphile-build-pg/src/plugins/PgRefsPlugin.ts +++ b/graphile-build/graphile-build-pg/src/plugins/PgRefsPlugin.ts @@ -1,4 +1,8 @@ import type { + AnyPgCodec, + AnyPgCodecRelationConfig, + AnyPgResourceOptions, + DefaultPgCodecRelationConfig, PgCodec, PgCodecExtensions, PgCodecRefPath, @@ -202,7 +206,7 @@ export const PgRefsPlugin: GraphileConfig.Plugin = { ? [tags.refVia] : null; - const refDefinitions = (resourceOptions.codec as PgCodec).extensions + const refDefinitions = resourceOptions.codec.extensions ?.refDefinitions; if (!refDefinitions) { if (rawRefVias) { @@ -252,16 +256,13 @@ export const PgRefsPlugin: GraphileConfig.Plugin = { outerLoop: for (const via of vias) { const path: PgCodecRefPath = []; const parts = via.split(";"); - let currentResourceOptions: PgResourceOptions = resourceOptions; + let currentResourceOptions = resourceOptions; for (const rawPart of parts) { - type RelationEntry = [ - string, - PgCodecRelationConfig, - ]; + type RelationEntry = [string, DefaultPgCodecRelationConfig]; const relations = registryConfig.pgRelations[currentResourceOptions.codec.name]; const relationEntries = relations - ? (Object.entries(relations) as Array) + ? Object.entries(relations) : []; const part = rawPart.trim(); // ENHANCE: allow whitespace diff --git a/graphile-build/graphile-build-pg/src/plugins/PgRegistryPlugin.ts b/graphile-build/graphile-build-pg/src/plugins/PgRegistryPlugin.ts index 2ba98c26a9..a0313d00da 100644 --- a/graphile-build/graphile-build-pg/src/plugins/PgRegistryPlugin.ts +++ b/graphile-build/graphile-build-pg/src/plugins/PgRegistryPlugin.ts @@ -1,5 +1,11 @@ /* eslint-disable graphile-export/export-instances */ -import type { PgRegistry, PgRegistryBuilder } from "@dataplan/pg"; +import type { + DefaultPgRegistry, + PgRegistry, + PgRegistryBuilder, + DefaultRegistryBuilder, + EmptyRegistryBuilder, +} from "@dataplan/pg"; import { makeRegistryBuilder } from "@dataplan/pg"; import type { PromiseOrDirect } from "grafast"; import { gatherConfig } from "graphile-build"; @@ -10,29 +16,29 @@ declare global { namespace GraphileConfig { interface GatherHelpers { pgRegistry: { - getRegistryBuilder(): PromiseOrDirect>; - getRegistry(): PromiseOrDirect>; + getRegistryBuilder(): PromiseOrDirect; + getRegistry(): PromiseOrDirect; }; } interface GatherHooks { pgRegistry_PgRegistryBuilder_init(event: { - registryBuilder: PgRegistryBuilder; + registryBuilder: EmptyRegistryBuilder; }): PromiseOrDirect; pgRegistry_PgRegistryBuilder_pgCodecs(event: { - registryBuilder: PgRegistryBuilder; + registryBuilder: DefaultRegistryBuilder; }): PromiseOrDirect; pgRegistry_PgRegistryBuilder_pgResources(event: { - registryBuilder: PgRegistryBuilder; + registryBuilder: DefaultRegistryBuilder; }): PromiseOrDirect; pgRegistry_PgRegistryBuilder_pgRelations(event: { - registryBuilder: PgRegistryBuilder; + registryBuilder: DefaultRegistryBuilder; }): PromiseOrDirect; pgRegistry_PgRegistryBuilder_finalize(event: { - registryBuilder: PgRegistryBuilder; + registryBuilder: DefaultRegistryBuilder; }): PromiseOrDirect; pgRegistry_PgRegistry(event: { - registry: PgRegistry; + registry: DefaultPgRegistry; }): PromiseOrDirect; } } diff --git a/graphile-build/graphile-build-pg/src/plugins/PgRelationsPlugin.ts b/graphile-build/graphile-build-pg/src/plugins/PgRelationsPlugin.ts index a7851af264..1330350aa5 100644 --- a/graphile-build/graphile-build-pg/src/plugins/PgRelationsPlugin.ts +++ b/graphile-build/graphile-build-pg/src/plugins/PgRelationsPlugin.ts @@ -2,17 +2,18 @@ import "./PgTablesPlugin.js"; import "graphile-config"; import type { - PgCodec, + DefaultPgCodec, + DefaultPgCodecRelationConfig, + DefaultPgRegistry, + DefaultPgResource, + DefaultPgResourceOptions, + DefaultPgSelectSingleStep, PgCodecRef, PgCodecRefPath, - PgCodecRelation, PgCodecRelationConfig, PgCodecWithAttributes, PgRefDefinition, - PgRegistry, - PgResource, PgResourceOptions, - PgSelectSingleStep, PgUnionAllStepConfigAttributes, PgUnionAllStepMember, } from "@dataplan/pg"; @@ -38,8 +39,8 @@ declare global { pgMutationPayloadRelations?: boolean; } interface PgRelationsPluginRelationDetails { - registry: PgRegistry; - codec: PgCodecWithAttributes; + registry: DefaultPgRegistry; + codec: DefaultPgCodec; relationName: string; } @@ -106,7 +107,7 @@ declare global { event: { pgClass: PgClass; serviceName: string; - resourceOptions: PgResourceOptions; + resourceOptions: DefaultPgResourceOptions; }, pgConstraint: PgConstraint, isReferencee?: boolean, @@ -118,7 +119,7 @@ declare global { serviceName: string; pgClass: PgClass; pgConstraint: PgConstraint; - relation: PgCodecRelationConfig; + relation: DefaultPgCodecRelationConfig; }): Promise | void; } } @@ -321,10 +322,10 @@ export const PgRelationsPlugin: GraphileConfig.Plugin = { ), ); const foreignResourceOptions = - (await info.helpers.pgTables.getResourceOptions( + await info.helpers.pgTables.getResourceOptions( serviceName, foreignClass, - )) as PgResourceOptions; + ); if ( !localCodec || !foreignResourceOptions || @@ -402,7 +403,8 @@ export const PgRelationsPlugin: GraphileConfig.Plugin = { : tags.forwardBehavior; const behavior = combineBehaviors(baseBehavior, specificBehavior); const newRelation: PgCodecRelationConfig = { - localCodec: localCodec as PgCodecWithAttributes, + name: relationName, + localCodec, localCodecPolymorphicTypes, localAttributes: localAttributes.map((c) => c!.attname), remoteAttributes: foreignAttributes.map((c) => c!.attname), @@ -455,7 +457,7 @@ export const PgRelationsPlugin: GraphileConfig.Plugin = { } } registryBuilder.addRelation( - codec as PgCodecWithAttributes, + codec, relationName, newRelation.remoteResourceOptions, newRelation, @@ -565,7 +567,7 @@ function makeSpecString( function makeRelationPlans( localAttributes: readonly string[], remoteAttributes: readonly string[], - otherSource: PgResource, + otherSource: DefaultPgResource, isMutationPayload: boolean, ) { const recordOrResult = isMutationPayload @@ -588,16 +590,17 @@ function makeRelationPlans( : null; const specFromRecord = EXPORTABLE( - (localAttributes, remoteAttributes) => ($record: PgSelectSingleStep) => { - return remoteAttributes.reduce((memo, remoteAttributeName, i) => { - memo[remoteAttributeName] = $record.get(localAttributes[i] as string); - return memo; - }, Object.create(null)); - }, + (localAttributes, remoteAttributes) => + ($record: DefaultPgSelectSingleStep) => { + return remoteAttributes.reduce((memo, remoteAttributeName, i) => { + memo[remoteAttributeName] = $record.get(localAttributes[i] as string); + return memo; + }, Object.create(null)); + }, [localAttributes, remoteAttributes], ); type MutationPayload = ObjectStep<{ - result: PgSelectSingleStep; + result: DefaultPgSelectSingleStep; }>; const singleRecordPlan = @@ -622,7 +625,7 @@ return function (otherSource) { ) : EXPORTABLE( (otherSource, specFromRecord) => - function plan($record: PgSelectSingleStep) { + function plan($record: DefaultPgSelectSingleStep) { return otherSource.get(specFromRecord($record)); }, [otherSource, specFromRecord], @@ -649,7 +652,7 @@ return function (otherSource) { ) : EXPORTABLE( (otherSource, specFromRecord) => - function plan($record: PgSelectSingleStep) { + function plan($record: DefaultPgSelectSingleStep) { return otherSource.find(specFromRecord($record)); }, [otherSource, specFromRecord], @@ -679,7 +682,7 @@ return function (otherSource, connection) { ) : EXPORTABLE( (connection, otherSource, specFromRecord) => - function plan($record: PgSelectSingleStep) { + function plan($record: DefaultPgSelectSingleStep) { return connection(otherSource.find(specFromRecord($record))); }, [connection, otherSource, specFromRecord], @@ -722,7 +725,7 @@ function addRelations( ? scope.pgPolymorphicSingleTableType : undefined; - const codec = (pgTypeResource?.codec ?? pgCodec) as PgCodecWithAttributes; + const codec = pgTypeResource?.codec ?? pgCodec; if ( (isMutationPayload && !build.options.pgMutationPayloadRelations) || !(isPgClassType || isMutationPayload || pgPolymorphism) || @@ -732,11 +735,8 @@ function addRelations( return fields; } const resource = pgTypeResource ?? build.pgTableResource(codec); - const relations: Record = (build.input.pgRegistry - .pgRelations[codec.name] ?? Object.create(null)) as Record< - string, - PgCodecRelation - >; + const relations = + build.input.pgRegistry.pgRelations[codec.name] ?? Object.create(null); if (resource && resource.parameters && !resource.isUnique) { return fields; @@ -747,7 +747,7 @@ function addRelations( refName: string; refDefinition: PgRefDefinition; ref?: PgCodecRef; - codec?: PgCodec; + codec?: DefaultPgCodec; }> = isMutationPayload ? [] : codec.refs @@ -783,8 +783,8 @@ function addRelations( listFieldName: string; connectionFieldName: string; description?: string; - pgResource?: PgResource; - pgCodec: PgCodec | undefined; + pgResource?: DefaultPgResource; + pgCodec: DefaultPgCodec | undefined; pgRelationDetails?: GraphileBuild.PgRelationsPluginRelationDetails; relatedTypeName: string; isNonNull: boolean | undefined; @@ -847,7 +847,7 @@ function addRelations( const { singleRecordPlan, listPlan, connectionPlan } = makeRelationPlans( localAttributes as string[], remoteAttributes as string[], - remoteResource as PgResource, + remoteResource, isMutationPayload ?? false, ); const singleRecordFieldName = relation.isReferencee @@ -909,8 +909,8 @@ function addRelations( } of refDefinitionList) { const isUnique = !!refSpec.singular; let hasReferencee; - let sharedCodec: PgCodec | undefined = undefined; - let sharedSource: PgResource | undefined = undefined; + let sharedCodec: DefaultPgCodec | undefined = undefined; + let sharedSource: DefaultPgResource | undefined = undefined; let behavior: string; let typeName: string | null | undefined; let singleRecordPlan; @@ -996,14 +996,12 @@ function addRelations( // Add forbidden names here if (ref.paths.length === 1 && ref.paths[0].length === 1) { - const relation: PgCodecRelation = resource.getRelation( - ref.paths[0][0].relationName, - ); + const relation = resource.getRelation(ref.paths[0][0].relationName); const remoteResource = relation.remoteResource; return makeRelationPlans( relation.localAttributes as string[], relation.remoteAttributes as string[], - remoteResource as PgResource, + remoteResource, isMutationPayload ?? false, ); } else if (!needsPgUnionAll) { @@ -1134,7 +1132,7 @@ function addRelations( const isConnection = mode === "connection"; const attributes: PgUnionAllStepConfigAttributes = unionAttributes ?? {}; - const resourceByTypeName: Record = + const resourceByTypeName: Record = Object.create(null); const members: PgUnionAllStepMember[] = []; for (const path of paths) { @@ -1173,9 +1171,11 @@ function addRelations( ($parent: ExecutableStep) => { const $record = isMutationPayload ? ( - $parent as ObjectStep<{ result: PgSelectSingleStep }> + $parent as ObjectStep<{ + result: DefaultPgSelectSingleStep; + }> ).get("result") - : ($parent as PgSelectSingleStep); + : ($parent as DefaultPgSelectSingleStep); for (let i = 0, l = paths.length; i < l; i++) { const path = paths[i]; const firstLayer = path.layers[0]; diff --git a/graphile-build/graphile-build-pg/src/plugins/PgRowByUniquePlugin.ts b/graphile-build/graphile-build-pg/src/plugins/PgRowByUniquePlugin.ts index c57db45798..1072973eec 100644 --- a/graphile-build/graphile-build-pg/src/plugins/PgRowByUniquePlugin.ts +++ b/graphile-build/graphile-build-pg/src/plugins/PgRowByUniquePlugin.ts @@ -1,10 +1,9 @@ import "graphile-config"; import type { - PgCodec, - PgCodecWithAttributes, - PgResource, - PgResourceUnique, + DefaultPgCodec, + DefaultPgResource, + DefaultPgResourceUnique, } from "@dataplan/pg"; import type { FieldArgs } from "grafast"; import { EXPORTABLE } from "graphile-build"; @@ -19,8 +18,8 @@ declare global { rowByUnique( this: Inflection, details: { - unique: PgResourceUnique; - resource: PgResource; + unique: DefaultPgResourceUnique; + resource: DefaultPgResource; }, ): string; } @@ -84,65 +83,58 @@ export const PgRowByUniquePlugin: GraphileConfig.Plugin = { return resources.reduce( (outerMemo, rawResource) => build.recoverable(outerMemo, () => - (rawResource.uniques as PgResourceUnique[]).reduce( - (memo, unique) => { - const resource = rawResource as PgResource< - any, - PgCodecWithAttributes, - any, - any, - any - >; - const uniqueKeys = unique.attributes as string[]; - const fieldName = build.inflection.rowByUnique({ - unique, - resource, - }); + rawResource.uniques.reduce((memo, unique) => { + const resource = rawResource; + const uniqueKeys = unique.attributes; + const fieldName = build.inflection.rowByUnique({ + unique, + resource, + }); - const type = build.getTypeByName( - build.inflection.tableType(resource.codec), - ); - if (!type || !(type instanceof GraphQLObjectType)) { - return memo; - } + const type = build.getTypeByName( + build.inflection.tableType(resource.codec), + ); + if (!type || !(type instanceof GraphQLObjectType)) { + return memo; + } - const detailsByAttributeName: { - [attributeName: string]: { - graphqlName: string; - codec: PgCodec; - }; - } = Object.create(null); - uniqueKeys.forEach((attributeName) => { - const attribute = resource.codec.attributes![attributeName]; - const attributeArgName = build.inflection.attribute({ - attributeName, - codec: resource.codec, - }); - detailsByAttributeName[attributeName] = { - graphqlName: attributeArgName, - codec: attribute.codec, - }; + const detailsByAttributeName: { + [attributeName: string]: { + graphqlName: string; + codec: DefaultPgCodec; + }; + } = Object.create(null); + uniqueKeys.forEach((attributeName) => { + const attribute = resource.codec.attributes![attributeName]; + const attributeArgName = build.inflection.attribute({ + attributeName, + codec: resource.codec, }); + detailsByAttributeName[attributeName] = { + graphqlName: attributeArgName, + codec: attribute.codec, + }; + }); - const attributeNames = Object.keys(detailsByAttributeName); - const clean = attributeNames.every( - (key) => - isSafeObjectPropertyName(key) && - isSafeObjectPropertyName( - detailsByAttributeName[key].graphqlName, - ), - ); - const plan = clean - ? /* - * Since all the identifiers are nice and clean we can use - * an optimized function that doesn't loop over the - * attributes and just builds the object directly. This is - * more performant, but it also makes the code nicer to - * read in the exported code. - */ - // eslint-disable-next-line graphile-export/exhaustive-deps - EXPORTABLE( - te.run`\ + const attributeNames = Object.keys(detailsByAttributeName); + const clean = attributeNames.every( + (key) => + isSafeObjectPropertyName(key) && + isSafeObjectPropertyName( + detailsByAttributeName[key].graphqlName, + ), + ); + const plan = clean + ? /* + * Since all the identifiers are nice and clean we can use + * an optimized function that doesn't loop over the + * attributes and just builds the object directly. This is + * more performant, but it also makes the code nicer to + * read in the exported code. + */ + // eslint-disable-next-line graphile-export/exhaustive-deps + EXPORTABLE( + te.run`\ return function (resource) { return (_$root, args) => resource.get({ ${te.join( attributeNames.map( @@ -154,76 +146,72 @@ return function (resource) { ", ", )} }); }` as any, - [resource], - ) - : EXPORTABLE( - (detailsByAttributeName, resource) => - function plan(_$root: any, args: FieldArgs) { - const spec = Object.create(null); - for (const attributeName in detailsByAttributeName) { - spec[attributeName] = args.get( - detailsByAttributeName[attributeName] - .graphqlName, - ); - } - return resource.get(spec); - }, - [detailsByAttributeName, resource], - ); - - const fieldBehaviorScope = "query:resource:single"; - if ( - !build.behavior.pgResourceUniqueMatches( - [resource, unique], - fieldBehaviorScope, + [resource], ) - ) { - return memo; - } - - return build.extend( - memo, - { - [fieldName]: fieldWithHooks( - { - fieldName, - fieldBehaviorScope, + : EXPORTABLE( + (detailsByAttributeName, resource) => + function plan(_$root: any, args: FieldArgs) { + const spec = Object.create(null); + for (const attributeName in detailsByAttributeName) { + spec[attributeName] = args.get( + detailsByAttributeName[attributeName].graphqlName, + ); + } + return resource.get(spec); }, - () => ({ - description: `Get a single \`${type.name}\`.`, - deprecationReason: tagToString( - resource.extensions?.tags?.deprecated, - ), - type, - args: uniqueKeys.reduce((args, attributeName) => { - const details = - detailsByAttributeName[attributeName]; - const attributeType = build.getGraphQLTypeByPgCodec( - details.codec, - "input", + [detailsByAttributeName, resource], + ); + + const fieldBehaviorScope = "query:resource:single"; + if ( + !build.behavior.pgResourceUniqueMatches( + [resource, unique], + fieldBehaviorScope, + ) + ) { + return memo; + } + + return build.extend( + memo, + { + [fieldName]: fieldWithHooks( + { + fieldName, + fieldBehaviorScope, + }, + () => ({ + description: `Get a single \`${type.name}\`.`, + deprecationReason: tagToString( + resource.extensions?.tags?.deprecated, + ), + type, + args: uniqueKeys.reduce((args, attributeName) => { + const details = detailsByAttributeName[attributeName]; + const attributeType = build.getGraphQLTypeByPgCodec( + details.codec, + "input", + ); + if (!attributeType) { + throw new Error( + `Could not determine type for attribute`, ); - if (!attributeType) { - throw new Error( - `Could not determine type for attribute`, - ); - } - args[details.graphqlName] = { - type: new GraphQLNonNull(attributeType), - }; - return args; - }, Object.create(null)), + } + args[details.graphqlName] = { + type: new GraphQLNonNull(attributeType), + }; + return args; + }, Object.create(null)), - plan: plan as any, - }), - ), - }, - `Adding row accessor for ${resource} by unique attributes ${uniqueKeys.join( - ",", - )}`, - ); - }, - outerMemo, - ), + plan: plan as any, + }), + ), + }, + `Adding row accessor for ${resource} by unique attributes ${uniqueKeys.join( + ",", + )}`, + ); + }, outerMemo), ), fields, ); diff --git a/graphile-build/graphile-build-pg/src/plugins/PgTableNodePlugin.ts b/graphile-build/graphile-build-pg/src/plugins/PgTableNodePlugin.ts index 54928bdf5e..50f7a55c6d 100644 --- a/graphile-build/graphile-build-pg/src/plugins/PgTableNodePlugin.ts +++ b/graphile-build/graphile-build-pg/src/plugins/PgTableNodePlugin.ts @@ -1,11 +1,9 @@ import "graphile-config"; import type { - PgCodec, - PgCodecWithAttributes, - PgResource, - PgResourceUnique, - PgSelectSingleStep, + AnyPgSelectSingleStep, + DefaultPgCodec, + DefaultPgResource, } from "@dataplan/pg"; import type { ListStep } from "grafast"; import { access, constant, list } from "grafast"; @@ -43,9 +41,7 @@ export const PgTableNodePlugin: GraphileConfig.Plugin = { codec.polymorphism.mode === "single" || codec.polymorphism.mode === "relational") ) { - const resource = build.pgTableResource( - codec as PgCodecWithAttributes, - ); + const resource = build.pgTableResource(codec); if (resource && resource.uniques?.length >= 1) { if (codec.polymorphism) { newBehavior.push("interface:node"); @@ -80,7 +76,7 @@ export const PgTableNodePlugin: GraphileConfig.Plugin = { ); }); - const resourcesByCodec = new Map(); + const resourcesByCodec = new Map(); for (const resource of tableResources) { let list = resourcesByCodec.get(resource.codec); if (!list) { @@ -110,7 +106,7 @@ export const PgTableNodePlugin: GraphileConfig.Plugin = { continue; } const pgResource = resources[0]; - const primaryKey = (pgResource.uniques as PgResourceUnique[]).find( + const primaryKey = pgResource.uniques.find( (u) => u.isPrimary === true, ); if (!primaryKey) { @@ -154,7 +150,7 @@ return function (list, constant) { ) : EXPORTABLE( (constant, identifier, list, pk) => - ($record: PgSelectSingleStep) => { + ($record: AnyPgSelectSingleStep) => { return list([ constant(identifier, false), ...pk.map((attribute) => $record.get(attribute)), diff --git a/graphile-build/graphile-build-pg/src/plugins/PgTablesPlugin.ts b/graphile-build/graphile-build-pg/src/plugins/PgTablesPlugin.ts index 5c22360cdd..c82c2ec55b 100644 --- a/graphile-build/graphile-build-pg/src/plugins/PgTablesPlugin.ts +++ b/graphile-build/graphile-build-pg/src/plugins/PgTablesPlugin.ts @@ -1,9 +1,12 @@ import type { + AnyPgCodecAttribute, + AnyPgResource, + DefaultPgResourceOptions, + AnyPgResourceUnique, PgCodec, - PgCodecAttribute, PgResource, - PgResourceOptions, - PgResourceUnique, + AnyPgResourceOptions, + DefaultPgCodec, } from "@dataplan/pg"; import { assertPgClassSingleStep, makePgResourceOptions } from "@dataplan/pg"; import { object } from "grafast"; @@ -87,7 +90,7 @@ declare global { */ _codecName( this: Inflection, - codec: PgCodec, + codec: DefaultPgCodec, ): string; /** @@ -103,7 +106,7 @@ declare global { */ _singularizedCodecName( this: Inflection, - codec: PgCodec, + codec: DefaultPgCodec, ): string; /** @@ -119,17 +122,17 @@ declare global { */ tableType( this: GraphileBuild.Inflection, - codec: PgCodec, + codec: DefaultPgCodec, ): string; tableConnectionType( this: GraphileBuild.Inflection, - codec: PgCodec, + codec: DefaultPgCodec, ): string; tableEdgeType( this: GraphileBuild.Inflection, - codec: PgCodec, + codec: DefaultPgCodec, ): string; patchType(this: GraphileBuild.Inflection, typeName: string): string; @@ -137,21 +140,21 @@ declare global { } interface ScopeObject { - pgCodec?: PgCodec; + pgCodec?: DefaultPgCodec; isPgClassType?: boolean; isPgConnectionRelated?: true; } interface ScopeObjectFieldsField { pgFieldResource?: PgResource; - pgFieldCodec?: PgCodec; - pgFieldAttribute?: PgCodecAttribute; + pgFieldCodec?: DefaultPgCodec; + pgFieldAttribute?: AnyPgCodecAttribute; isPgFieldConnection?: boolean; isPgFieldSimpleCollection?: boolean; } interface ScopeInterfaceFieldsField { pgFieldResource?: PgResource; - pgFieldCodec?: PgCodec; - pgFieldAttribute?: PgCodecAttribute; + pgFieldCodec?: DefaultPgCodec; + pgFieldAttribute?: AnyPgCodecAttribute; isPgFieldConnection?: boolean; isPgFieldSimpleCollection?: boolean; } @@ -165,7 +168,7 @@ declare global { getResourceOptions( serviceName: string, pgClass: PgClass, - ): Promise; + ): Promise; }; } @@ -177,7 +180,7 @@ declare global { serviceName: string; pgClass: PgClass; pgConstraint: PgConstraint; - unique: PgResourceUnique; + unique: AnyPgResourceUnique; }): void | Promise; /** * Passed the PgResourceOptions before it's added to the PgRegistryBuilder. @@ -185,17 +188,17 @@ declare global { pgTables_PgResourceOptions(event: { serviceName: string; pgClass: PgClass; - resourceOptions: PgResourceOptions; + resourceOptions: DefaultPgResourceOptions; }): void | Promise; pgTables_PgResourceOptions_relations(event: { serviceName: string; pgClass: PgClass; - resourceOptions: PgResourceOptions; + resourceOptions: DefaultPgResourceOptions; }): Promise | void; pgTables_PgResourceOptions_relations_post(event: { serviceName: string; pgClass: PgClass; - resourceOptions: PgResourceOptions; + resourceOptions: DefaultPgResourceOptions; }): Promise | void; } } @@ -204,11 +207,11 @@ declare global { interface State { resourceOptionsByPgClassByService: Map< string, - Map> + Map> >; - resourceByResourceOptions: Map>; + resourceByResourceOptions: Map>; detailsByResourceOptions: Map< - PgResourceOptions, + DefaultPgResourceOptions, { serviceName: string; pgClass: PgClass } >; } @@ -406,7 +409,7 @@ export const PgTablesPlugin: GraphileConfig.Plugin = { uniqueAttributeOnlyConstraints.map(async (pgConstraint) => { const { tags, description } = pgConstraint.getTagsAndDescription(); - const unique: PgResourceUnique = { + const unique: AnyPgResourceUnique = { isPrimary: pgConstraint.contype === "p", attributes: pgConstraint.conkey!.map( (k) => attributes.find((att) => att.attnum === k)!.attname, @@ -543,7 +546,7 @@ export const PgTablesPlugin: GraphileConfig.Plugin = { await info.helpers.pgIntrospection.getIntrospection(); const toProcess: Array<{ - resourceOptions: PgResourceOptions; + resourceOptions: AnyPgResourceOptions; pgClass: PgClass; serviceName: string; }> = []; diff --git a/graphile-build/graphile-build-pg/src/utils.ts b/graphile-build/graphile-build-pg/src/utils.ts index 273293efff..f382c701a5 100644 --- a/graphile-build/graphile-build-pg/src/utils.ts +++ b/graphile-build/graphile-build-pg/src/utils.ts @@ -385,9 +385,7 @@ export const resolveResourceRefPath = ( layers: [] as Layer[], }; for (const pathEntry of path) { - const relation = result.resource.getRelation( - pathEntry.relationName, - ) as PgCodecRelation; + const relation = result.resource.getRelation(pathEntry.relationName); const { isReferencee, localAttributes, diff --git a/graphile-build/graphile-simplify-inflection/src/index.ts b/graphile-build/graphile-simplify-inflection/src/index.ts index ac3653ed46..9504da3024 100644 --- a/graphile-build/graphile-simplify-inflection/src/index.ts +++ b/graphile-build/graphile-simplify-inflection/src/index.ts @@ -320,9 +320,9 @@ const PgSimplifyInflectionPlugin: GraphileConfig.Plugin = { if (baseName) { return this.camelCase(baseName); } - const foreignPk = ( - relation.remoteResource.uniques as PgResourceUnique[] - ).find((u) => u.isPrimary); + const foreignPk = relation.remoteResource.uniques.find( + (u) => u.isPrimary, + ); if ( foreignPk && arraysMatch(foreignPk.attributes, relation.remoteAttributes) @@ -375,8 +375,7 @@ const PgSimplifyInflectionPlugin: GraphileConfig.Plugin = { `singleRelationBackwards inflector check failed: multiple table-like resources for codec '${codec.name}', so we cannot determine the primary key - please override this inflector for this table.`, ); } - const uniques = (possibleResources[0]?.uniques ?? - []) as PgResourceUnique[]; + const uniques = possibleResources[0]?.uniques ?? []; const pk = uniques.find((u) => u.isPrimary); if (pk && arraysMatch(pk.attributes, relation.localAttributes)) { return this.camelCase( @@ -417,9 +416,7 @@ const PgSimplifyInflectionPlugin: GraphileConfig.Plugin = { ); } } - const pk = (relation.remoteResource.uniques as PgResourceUnique[]).find( - (u) => u.isPrimary, - ); + const pk = relation.remoteResource.uniques.find((u) => u.isPrimary); if (pk && arraysMatch(pk.attributes, relation.remoteAttributes)) { return this.camelCase( `${this.distinctPluralize( diff --git a/graphile-build/graphile-utils/src/makeAddPgTableConditionPlugin.ts b/graphile-build/graphile-utils/src/makeAddPgTableConditionPlugin.ts index 02b5ee4c7d..375e070f87 100644 --- a/graphile-build/graphile-utils/src/makeAddPgTableConditionPlugin.ts +++ b/graphile-build/graphile-utils/src/makeAddPgTableConditionPlugin.ts @@ -1,4 +1,8 @@ -import type { PgConditionStep, PgSelectStep } from "@dataplan/pg"; +import type { + DefaultPgSelectStep, + PgConditionStep, + PgSelectStep, +} from "@dataplan/pg"; import type { FieldArgs, GrafastInputFieldConfig } from "grafast"; import type { SQL, sql } from "pg-sql2"; @@ -13,7 +17,7 @@ export function makeAddPgTableConditionPlugin( conditionGenerator?: ( value: FieldArgs, helpers: { - $condition: PgConditionStep; + $condition: PgConditionStep; sql: typeof sql; sqlTableAlias: SQL; build: GraphileBuild.Build; @@ -85,7 +89,7 @@ export function makeAddPgTableConditionPlugin( conditionFieldSpec.applyPlan = EXPORTABLE( (build, conditionGenerator, sql) => function applyPlan( - $condition: PgConditionStep, + $condition: PgConditionStep, val, ) { const expression = conditionGenerator!(val, { diff --git a/graphile-build/graphile-utils/src/makeAddPgTableOrderByPlugin.ts b/graphile-build/graphile-utils/src/makeAddPgTableOrderByPlugin.ts index ee3e9bee19..95638be761 100644 --- a/graphile-build/graphile-utils/src/makeAddPgTableOrderByPlugin.ts +++ b/graphile-build/graphile-utils/src/makeAddPgTableOrderByPlugin.ts @@ -1,17 +1,17 @@ -import type { PgOrderSpec, PgSelectStep } from "@dataplan/pg"; +import type { DefaultPgSelectStep, PgOrderSpec, PgSelectStep } from "@dataplan/pg"; import { EXPORTABLE } from "./exportable.js"; type OrderBySpecIdentity = | string // Attribute name | Omit // Expression - | (($select: PgSelectStep) => Omit); // Callback, allows for joins/etc + | (($select: DefaultPgSelectStep) => Omit); // Callback, allows for joins/etc export interface MakeAddPgTableOrderByPluginOrders { [orderByEnumValue: string]: { extensions: { grafast: { - applyPlan($select: PgSelectStep): void; + applyPlan($select: DefaultPgSelectStep): void; }; }; }; @@ -127,7 +127,7 @@ export function orderByAscDesc( ? "FIRST" : "LAST"; - type Plan = ($select: PgSelectStep) => void; + type Plan = ($select: DefaultPgSelectStep) => void; let spec: PgOrderSpec; const ascendingPlan: Plan = diff --git a/postgraphile/postgraphile/src/plugins/PgV4BehaviorPlugin.ts b/postgraphile/postgraphile/src/plugins/PgV4BehaviorPlugin.ts index 0f67ee06c2..62373de680 100644 --- a/postgraphile/postgraphile/src/plugins/PgV4BehaviorPlugin.ts +++ b/postgraphile/postgraphile/src/plugins/PgV4BehaviorPlugin.ts @@ -1,7 +1,7 @@ import "graphile-config"; import "graphile-build-pg"; -import type { PgResourceOptions } from "@dataplan/pg"; +import type { DefaultPgResourceOptions, PgResourceOptions } from "@dataplan/pg"; import type { PgProc } from "graphile-build-pg/pg-introspection"; import { inspect } from "util"; @@ -14,7 +14,7 @@ declare global { } const v4ComputedAttributeChecks = ( - _s: PgResourceOptions, + _s: DefaultPgResourceOptions, pgProc: PgProc, ): boolean => { const args = pgProc.getArguments(); diff --git a/postgraphile/postgraphile/src/plugins/PgV4InflectionPlugin.ts b/postgraphile/postgraphile/src/plugins/PgV4InflectionPlugin.ts index 90633efcb4..c2107f5aa6 100644 --- a/postgraphile/postgraphile/src/plugins/PgV4InflectionPlugin.ts +++ b/postgraphile/postgraphile/src/plugins/PgV4InflectionPlugin.ts @@ -27,13 +27,13 @@ export const PgV4InflectionPlugin: GraphileConfig.Plugin = { }, _attributeName(previous, options, details) { const { codec, attributeName } = details; - const attribute = codec.attributes[attributeName]; + const attribute = codec.attributes![attributeName]; if (!attribute) { throw new Error( `Attempted to access attribute '${attributeName}' of codec '${ codec.name }', but it doesn't have that attribute (known attributes: ${Object.keys( - codec.attributes, + codec.attributes!, ).join(", ")})`, ); } diff --git a/postgraphile/postgraphile/src/presets/relay.ts b/postgraphile/postgraphile/src/presets/relay.ts index a13a79e02a..83cee34b3d 100644 --- a/postgraphile/postgraphile/src/presets/relay.ts +++ b/postgraphile/postgraphile/src/presets/relay.ts @@ -51,7 +51,7 @@ export const PgRelayPlugin: GraphileConfig.Plugin = { entityBehavior: { pgCodecAttribute(behavior, [codec, attributeName], build) { const newBehavior = [behavior]; - const attr = codec.attributes[attributeName]; + const attr = codec.attributes![attributeName]; const resource = codec.polymorphism?.mode === "union" @@ -88,9 +88,9 @@ export const PgRelayPlugin: GraphileConfig.Plugin = { // If the column is available via a singular relation, don't include the column itself const relationsMap = build.input.pgRegistry.pgRelations[codec.name]; const relations = relationsMap - ? (Object.values( + ? Object.values( build.input.pgRegistry.pgRelations[codec.name] ?? {}, - ) as PgCodecRelation[]) + ) : []; const singularRelationsUsingThisColumn = relations.filter((r) => { // NOTE: We do this even if the end table is not visible, because @@ -110,10 +110,8 @@ export const PgRelayPlugin: GraphileConfig.Plugin = { newBehavior.push(...RELAY_HIDDEN_COLUMN_BEHAVIORS); } } - const relations = ( - Object.values( - build.input.pgRegistry.pgRelations[codec.name] ?? {}, - ) as PgCodecRelation[] + const relations = Object.values( + build.input.pgRegistry.pgRelations[codec.name] ?? {}, ).filter((r) => !r.isReferencee && r.isUnique); const isPartOfRelation = !attr.codec.extensions?.isEnumTableEnum && diff --git a/postgraphile/postgraphile/src/presets/v4.ts b/postgraphile/postgraphile/src/presets/v4.ts index b626db9f7a..08c8d26c59 100644 --- a/postgraphile/postgraphile/src/presets/v4.ts +++ b/postgraphile/postgraphile/src/presets/v4.ts @@ -154,7 +154,7 @@ const makeV4Plugin = (options: V4Options): GraphileConfig.Plugin => { entityBehavior: { pgResource: "+delete:resource:select", pgCodecAttribute(behavior, [codec, attributeName]) { - const attribute = codec.attributes[attributeName]; + const attribute = codec.attributes![attributeName]; const underlyingCodec = attribute.codec.domainOfCodec ?? attribute.codec; const newBehavior = [behavior]; diff --git a/utils/graphile/src/commands/behavior/debug/main.ts b/utils/graphile/src/commands/behavior/debug/main.ts index 04daf71d3c..a0ba62ff25 100644 --- a/utils/graphile/src/commands/behavior/debug/main.ts +++ b/utils/graphile/src/commands/behavior/debug/main.ts @@ -185,7 +185,7 @@ function getEntities( build: GraphileBuild.Build, entityType: keyof GraphileBuild.BehaviorEntities, ) { - const registry = build.input.pgRegistry as PgRegistry; + const registry = build.input.pgRegistry; switch (entityType) { case "pgCodec": { return registry.pgCodecs;