From 166fe5f7d4fe8ba54213b0e6a2943a1f1018dee4 Mon Sep 17 00:00:00 2001 From: Wessel van der Veen Date: Wed, 4 Oct 2023 12:01:08 +0200 Subject: [PATCH] rework types --- grafast/dataplan-pg/src/codecs.ts | 177 +++---- grafast/dataplan-pg/src/datasource.ts | 445 +++++++++------- .../dataplan-pg/src/examples/exampleSchema.ts | 43 +- grafast/dataplan-pg/src/interfaces.ts | 478 +++++++++--------- .../dataplan-pg/src/steps/pgSelectSingle.ts | 2 +- 5 files changed, 622 insertions(+), 523 deletions(-) diff --git a/grafast/dataplan-pg/src/codecs.ts b/grafast/dataplan-pg/src/codecs.ts index 2e94a5604c..3970f783a6 100644 --- a/grafast/dataplan-pg/src/codecs.ts +++ b/grafast/dataplan-pg/src/codecs.ts @@ -38,8 +38,12 @@ import { import type { PgExecutor } from "./executor.js"; import { inspect } from "./inspect.js"; import type { + AnyPgCodec, PgCodec, + PgCodecAttributes, + PgCodecAttributesRecord, PgCodecExtensions, + PgCodecFromPostgres, PgCodecPolymorphism, PgDecode, PgEncode, @@ -59,10 +63,21 @@ export type PgCodecAttributeVia = string | PgCodecAttributeViaExplicit; /** @deprecated Use DataplanPg.PgCodecAttributeExtensions instead */ export type PgCodecAttributeExtensions = DataplanPg.PgCodecAttributeExtensions; +export type AnyPgCodecAttribute = PgCodecAttribute; +export type PgCodecAttributeName = U extends PgCodecAttribute< + infer TName, + any, + any +> + ? TName + : never; + 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. */ @@ -129,14 +144,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 +167,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 +181,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 +324,13 @@ function recordStringToTuple(value: string): Array { return tuple; } -function realAttributeDefs( - attributes: TAttributes, -): Array<[string, TAttributes[keyof TAttributes]]> { +function realAttributeDefs< + TAttributes extends ReadonlyArray, +>( + attributes: PgCodecAttributesRecord, +): Array<[PgCodecAttributeName, TAttributes[number]]> { const attributeDefs = Object.entries(attributes) as Array< - [string, TAttributes extends infer U ? U[keyof U] : never] + [PgCodecAttributeName, TAttributes[number]] >; return attributeDefs.filter( ([_attributeName, spec]) => !spec.expression && !spec.via, @@ -336,8 +344,10 @@ function realAttributeDefs( * * @see {@link https://www.postgresql.org/docs/current/rowtypes.html#id-1.5.7.24.6} */ -function makeRecordToSQLRawValue( - attributes: TAttributes, +function makeRecordToSQLRawValue< + TAttributes extends ReadonlyArray, +>( + attributes: PgCodecAttributesRecord, ): PgEncode> { const attributeDefs = realAttributeDefs(attributes); return (value) => { @@ -350,19 +360,21 @@ 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 ReadonlyArray, +> = { + [TCodecAttribute in TAttributes[number] as PgCodecAttributeName]: 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,8 +383,10 @@ export type ObjectFromPgCodecAttributes = * * @see {@link https://www.postgresql.org/docs/current/rowtypes.html#id-1.5.7.24.6} */ -function makeSQLValueToRecord( - attributes: TAttributes, +function makeSQLValueToRecord< + TAttributes extends ReadonlyArray, +>( + attributes: PgCodecAttributesRecord, ): (value: string) => ObjectFromPgCodecAttributes { const attributeDefs = realAttributeDefs(attributes); const attributeCount = attributeDefs.length; @@ -390,12 +404,12 @@ function makeSQLValueToRecord( export type PgRecordTypeCodecSpec< TName extends string, - TAttributes extends PgCodecAttributes, + TAttributes extends ReadonlyArray, > = { name: TName; executor: PgExecutor; identifier: SQL; - attributes: TAttributes; + attributes: PgCodecAttributesRecord; polymorphism?: PgCodecPolymorphism; description?: string; extensions?: Partial; @@ -413,8 +427,8 @@ export type PgRecordTypeCodecSpec< * isAnonymous - if true, this represents an "anonymous" type, typically the return value of a function or something like that. If this is true, then name and identifier are ignored. */ export function recordCodec< - const TName extends string, - const TAttributes extends PgCodecAttributes, + TName extends string, + TAttributes extends ReadonlyArray, >( config: PgRecordTypeCodecSpec, ): PgCodec< @@ -422,9 +436,9 @@ export function recordCodec< TAttributes, string, ObjectFromPgCodecAttributes, - undefined, - undefined, - undefined + never, + never, + never > { const { name, @@ -504,14 +518,14 @@ type CodecWithListCodec< `${TCodec extends PgCodec ? UName : never}[]`, - undefined, + never, string, - TCodec extends PgCodec + TCodec extends PgCodec ? UFromJs[] : any[], TCodec, - undefined, - undefined + never, + never >; }; @@ -529,7 +543,7 @@ type CodecWithListCodec< * @param identifier - a pg-sql2 fragment that represents the name of this type */ export function listOfCodec< - TInnerCodec extends PgCodec, + TInnerCodec extends PgCodec, >( listedCodec: TInnerCodec, config?: { @@ -542,14 +556,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; @@ -568,22 +582,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< @@ -661,7 +667,7 @@ exportAs("@dataplan/pg", listOfCodec, "listOfCodec"); */ export function domainOfCodec< TName extends string, - TInnerCodec extends PgCodec, + TInnerCodec extends AnyPgCodec, >( innerCodec: TInnerCodec, name: TName, @@ -673,13 +679,15 @@ export function domainOfCodec< } = {}, ): PgCodec< TName, - TInnerCodec extends PgCodec ? U : any, - TInnerCodec extends PgCodec ? U : any, - undefined, + PgCodecAttributes, + PgCodecFromPostgres, + any, TInnerCodec, - undefined + never, + any > { const { description, extensions, notNull } = config; + return { // Generally same as underlying type: ...innerCodec, @@ -689,7 +697,7 @@ export function domainOfCodec< sqlType: identifier, description, extensions, - domainOfCodec: innerCodec.arrayOfCodec ? undefined : innerCodec, + ...(innerCodec.arrayOfCodec ? {} : { domainOfCodec: innerCodec }), notNull: Boolean(notNull), }; } @@ -701,7 +709,7 @@ exportAs("@dataplan/pg", domainOfCodec, "domainOfCodec"); * @internal */ function escapeRangeValue< - TInnerCodec extends PgCodec, + TInnerCodec extends PgCodec, >(value: null | any, innerCodec: TInnerCodec): string { if (value == null) { return ""; @@ -727,15 +735,7 @@ interface PgRange { */ export function rangeOfCodec< TName extends string, - TInnerCodec extends PgCodec< - any, - undefined, - any, - any, - undefined, - any, - undefined - >, + TInnerCodec extends PgCodec, >( innerCodec: TInnerCodec, name: TName, @@ -744,15 +744,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; @@ -1169,9 +1161,7 @@ export function getCodecByPgCatalogTypeName(pgCatalogTypeName: string) { return null; } -export function getInnerCodec< - TCodec extends PgCodec, ->( +export function getInnerCodec( codec: TCodec, ): TCodec extends PgCodec< any, @@ -1179,7 +1169,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..b39d313141 100644 --- a/grafast/dataplan-pg/src/datasource.ts +++ b/grafast/dataplan-pg/src/datasource.ts @@ -13,7 +13,8 @@ import type { SQL } from "pg-sql2"; import sql from "pg-sql2"; import type { - PgCodecAttributes, + AnyPgCodecAttribute, + PgCodecAttributeName, PgCodecAttributeVia, PgCodecAttributeViaExplicit, } from "./codecs.js"; @@ -28,17 +29,22 @@ import type { } from "./executor.js"; import { inspect } from "./inspect.js"; import type { - Expand, + AnyPgCodec, + AnyPgCodecRelationConfig, GetPgCodecAttributes, GetPgRegistryCodecRelations, - GetPgRegistryCodecs, PgCodec, + PgCodecName, PgCodecRelation, PgCodecRelationConfig, - PgCodecWithAttributes, + PgCodecRelationConfigLocalCodec, + PgCodecRelationConfigName, + PgRangeItemCodec, PgRefDefinition, PgRegistry, + PgRegistryCodecs, PgRegistryConfig, + PgRegistryRelations, PlanByUniques, } from "./interfaces.js"; import type { PgClassExpressionStep } from "./steps/pgClassExpression.js"; @@ -88,7 +94,7 @@ export type PgResourceParameterExtensions = */ export interface PgResourceParameter< TName extends string | null = string | null, - TCodec extends PgCodec = PgCodec, + TCodec extends AnyPgCodec = AnyPgCodec, > { /** * Name of the parameter, if null then we must use positional rather than @@ -115,12 +121,12 @@ export interface PgResourceParameter< * Description of a unique constraint on a PgResource. */ export interface PgResourceUnique< - TAttributes extends PgCodecAttributes = PgCodecAttributes, + TAttributes extends ReadonlyArray, > { /** * The attributes that are unique */ - attributes: ReadonlyArray; + attributes: ReadonlyArray>; /** * If this is true, this represents the "primary key" of the resource. */ @@ -152,12 +158,46 @@ export interface PgCodecRefs { [refName: string]: PgCodecRef; } +export type AnyPgResourceOptions = 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, + TCodec extends AnyPgCodec = AnyPgCodec, TUniques extends ReadonlyArray< PgResourceUnique> > = ReadonlyArray>>, @@ -177,9 +217,7 @@ 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; @@ -215,7 +253,7 @@ export interface PgResourceOptions< export interface PgFunctionResourceOptions< TNewName extends string = string, - TCodec extends PgCodec = PgCodec, + TCodec extends AnyPgCodec = AnyPgCodec, TUniques extends ReadonlyArray< PgResourceUnique> > = ReadonlyArray>>, @@ -231,37 +269,50 @@ export interface PgFunctionResourceOptions< uniques?: TUniques; extensions?: PgResourceExtensions; isMutation?: boolean; - selectAuth?: ( - $step: PgSelectStep>, - ) => void; + selectAuth?: ($step: PgSelectStep) => void; description?: string; } +export type AnyPgResource = PgResource; +export type PgResourceCodec = U extends PgResource< + any, + infer TCodec, + any, + any, + any, + any, + any +> + ? TCodec + : never; + /** * 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, + TName extends string, + TCodec extends AnyPgCodec, TUniques extends ReadonlyArray< PgResourceUnique> - > = ReadonlyArray>>, - TParameters extends readonly PgResourceParameter[] | undefined = - | readonly PgResourceParameter[] - | undefined, - TRegistry extends PgRegistry = PgRegistry, + >, + TParameters extends readonly PgResourceParameter[] | undefined, + TCodecs extends ReadonlyArray, + TResourceOptions extends ReadonlyArray, + TRelationConfigs extends ReadonlyArray, > { - public readonly registry: TRegistry; + public readonly registry: PgRegistry< + TCodecs, + TResourceOptions, + TRelationConfigs + >; public readonly codec: TCodec; public readonly executor: PgExecutor; public readonly name: TName; public readonly identifier: string; public readonly from: SQL | ((...args: PgSelectArgumentDigest[]) => SQL); public readonly uniques: TUniques; - private selectAuth?: ( - $step: PgSelectStep>, - ) => void; + private selectAuth?: ($step: PgSelectStep) => void; // TODO: make a public interface for this information /** @@ -291,7 +342,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 @@ -301,7 +352,7 @@ export class PgResource< * to understand. */ constructor( - registry: TRegistry, + registry: PgRegistry, options: PgResourceOptions, ) { const { @@ -370,7 +421,7 @@ export class PgResource< * type/relations/etc. */ static alternativeResourceOptions< - TCodec extends PgCodec, + TCodec extends AnyPgCodec, const TNewUniques extends ReadonlyArray< PgResourceUnique> >, @@ -382,7 +433,7 @@ export class PgResource< identifier?: string; from: SQL; uniques?: TNewUniques; - extensions?: PgResourceExtensions; + extensions?: DataplanPg.PgResourceExtensions; }, ): PgResourceOptions { const { name, identifier, from, uniques, extensions } = overrideOptions; @@ -407,7 +458,7 @@ export class PgResource< * type/relations/etc but pull their rows from functions. */ static functionResourceOptions< - TCodec extends PgCodec, + TCodec extends AnyPgCodec, const TNewParameters extends readonly PgResourceParameter[], const TNewUniques extends ReadonlyArray< PgResourceUnique> @@ -512,16 +563,22 @@ export class PgResource< return chalk.bold.blue(`PgResource(${this.name})`); } - public getRelations(): GetPgRegistryCodecRelations { - return (this.registry.pgRelations[this.codec.name] ?? + public getRelations(): GetPgRegistryCodecRelations< + PgRegistry, + TCodec + > { + return (this.registry.pgRelations[this.codec.name as PgCodecName] ?? Object.create(null)) as any; } public getRelation< - TRelationName extends keyof GetPgRegistryCodecRelations, + TRelationName extends PgCodecRelationConfigName, >( name: TRelationName, - ): GetPgRegistryCodecRelations[TRelationName] { + ): GetPgRegistryCodecRelations< + PgRegistry, + TCodec + >[TRelationName] { return this.getRelations()[name]; } @@ -534,9 +591,9 @@ export class PgResource< } if (typeof via === "string") { // Check - const relation = this.getRelation(via) as unknown as - | PgCodecRelation - | undefined; + const relation = this.getRelation( + via as PgCodecRelationConfigName, + ); if (!relation) { throw new Error(`Unknown relation '${via}' in ${this}`); } @@ -553,21 +610,26 @@ export class PgResource< // PERF: this needs optimization. public getReciprocal< - TOtherCodec extends GetPgRegistryCodecs, - TOtherRelationName extends keyof GetPgRegistryCodecRelations< - TRegistry, - TOtherCodec + TOtherCodec extends TCodecs[number], + TOtherRelationName extends PgCodecRelationConfigName< + TRelationConfigs[number] >, >( otherCodec: TOtherCodec, otherRelationName: TOtherRelationName, ): | [ - relationName: keyof GetPgRegistryCodecRelations, + relationName: keyof GetPgRegistryCodecRelations< + PgRegistry, + TCodec + >, relation: GetPgRegistryCodecRelations< - TRegistry, + PgRegistry, + TCodec + >[keyof GetPgRegistryCodecRelations< + PgRegistry, TCodec - >[keyof GetPgRegistryCodecRelations], + >], ] | null { if (this.parameters) { @@ -606,7 +668,7 @@ export class PgResource< spec: PlanByUniques, TUniques>, // This is internal, it's an optimisation we can use but you shouldn't. _internalOptionsDoNotPass?: PgSelectSinglePlanOptions, - ): GetPgCodecAttributes extends PgCodecAttributes + ): GetPgCodecAttributes extends PgCodecAttributes ? PgSelectSingleStep : PgClassExpressionStep { if (this.parameters) { @@ -823,67 +885,37 @@ export class PgResource< exportAs("@dataplan/pg", PgResource, "PgResource"); 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 ReadonlyArray, + TResourceOptions extends ReadonlyArray, + TRelationConfigs extends ReadonlyArray, > { getRegistryConfig(): PgRegistryConfig< - Expand, - Expand, - Expand + TCodecs, + TResourceOptions, + TRelationConfigs >; - addCodec( + addCodec( codec: TCodec, ): PgRegistryBuilder< - TCodecs & { - [name in TCodec["name"]]: TCodec; - }, - TResources, - TRelations + [...TCodecs, TCodec], + TResourceOptions, + TRelationConfigs >; - addResource>( - resource: TResource, + addResource( + resource: TResourceOption, ): PgRegistryBuilder< - TCodecs & { - [name in TResource["codec"]["name"]]: TResource["codec"]; - }, - TResources & { - [name in TResource["name"]]: TResource; - }, - TRelations + TCodecs, + [...TResourceOptions, TResourceOption], + TRelationConfigs >; addRelation< - TCodec extends PgCodec, + TCodec extends AnyPgCodec, const TCodecRelationName extends string, const TRemoteResource extends PgResourceOptions, const TCodecRelation extends Omit< - PgCodecRelationConfig, + PgCodecRelationConfig, "localCodec" | "remoteResourceOptions" >, >( @@ -893,52 +925,27 @@ export interface PgRegistryBuilder< relation: TCodecRelation, ): PgRegistryBuilder< TCodecs, - TResources, - TRelations & { - [codecName in TCodec["name"]]: { - [relationName in TCodecRelationName]: TCodecRelation & { - localCodec: TCodec; - remoteResourceOptions: TRemoteResource; - }; - }; - } + TResourceOptions, + [ + ...TRelationConfigs, + TCodecRelation & { + 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 - >; - }; - }, + TCodecs extends ReadonlyArray, + TResourceOptions extends ReadonlyArray, + TRelationConfigs extends ReadonlyArray, >( - config: PgRegistryConfig, -): PgRegistry { - const registry: PgRegistry = { + config: PgRegistryConfig, +): PgRegistry { + const registry: PgRegistry = { pgCodecs: Object.create(null) as any, pgResources: Object.create(null) as any, pgRelations: Object.create(null) as any, @@ -965,11 +972,37 @@ export function makeRegistry< }); let addCodecForbidden = false; - function addCodec(codec: PgCodec): PgCodec { + function addCodec< + TName extends string, + TAttributes extends PgCodecAttributes, + TFromPostgres, + TFromJavaScript, + TArrayItemCodec extends AnyPgCodec, + TDomainItemCodec extends AnyPgCodec, + TRangeItemCodec extends PgRangeItemCodec, + >( + codec: PgCodec< + TName, + TAttributes, + TFromPostgres, + TFromJavaScript, + TArrayItemCodec, + TDomainItemCodec, + TRangeItemCodec + >, + ): PgCodec< + TName, + TAttributes, + TFromPostgres, + TFromJavaScript, + TArrayItemCodec, + TDomainItemCodec, + TRangeItemCodec + > { if (addCodecForbidden) { throw new Error(`It's too late to call addCodec now`); } - const codecName = codec.name; + const codecName = codec.name as PgCodecName; if (registry.pgCodecs[codecName]) { if (registry.pgCodecs[codecName] !== codec) { console.dir({ @@ -982,14 +1015,14 @@ export function makeRegistry< } 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 as any; return codec; } else { // Custom spec, pin it back to the registry - registry.pgCodecs[codecName as keyof TCodecs] = codec as any; + registry.pgCodecs[codecName] = codec as any; if (codec.attributes) { - const prevCols = codec.attributes as PgCodecAttributes; + const prevCols = codec.attributes as PgCodecAttributes; for (const col of Object.values(prevCols)) { addCodec(col.codec); } @@ -1008,8 +1041,10 @@ export function makeRegistry< Object.defineProperties(codec, { $exporter$args: { value: [registry, codecName] }, $exporter$factory: { - value: (registry: PgRegistry, codecName: string) => - registry.pgCodecs[codecName], + value: ( + registry: PgRegistry, + codecName: PgCodecName, + ) => registry.pgCodecs[codecName], }, }); @@ -1017,7 +1052,9 @@ export function makeRegistry< } } - for (const [codecName, codecSpec] of Object.entries(config.pgCodecs)) { + for (const [codecName, codecSpec] of Object.entries(config.pgCodecs) as Array< + [PgCodecName, TCodecs[number]] + >) { if (codecName !== codecSpec.name) { throw new Error(`Codec added to registry with wrong name`); } @@ -1026,7 +1063,9 @@ export function makeRegistry< for (const [resourceName, rawConfig] of Object.entries( config.pgResources, - ) as [keyof TResourceOptions, PgResourceOptions][]) { + ) as Array< + [PgResourceOptionName, TResourceOptions[number]] + >) { const resourceConfig = { ...rawConfig, codec: addCodec(rawConfig.codec), @@ -1037,7 +1076,15 @@ export function makeRegistry< })) : rawConfig.parameters, }; - const resource = new PgResource(registry, resourceConfig) as any; + const resource = new PgResource< + PgResourceOptionName, + PgResourceOptionCodec, + PgResourceOptionUniques, + PgResourceOptionParameters, + TCodecs, + TResourceOptions, + TRelationConfigs + >(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,8 +1092,10 @@ export function makeRegistry< Object.defineProperties(resource, { $exporter$args: { value: [registry, resourceName] }, $exporter$factory: { - value: (registry: PgRegistry, resourceName: string) => - registry.pgResources[resourceName], + value: ( + registry: PgRegistry, + resourceName: PgResourceOptionName, + ) => registry.pgResources[resourceName], }, }); @@ -1083,11 +1132,13 @@ export function makeRegistry< * remove the ones that already have resources, then we build resources for the * remainder. */ - const tableLikeCodecsWithoutTableLikeResources = new Set(); + const tableLikeCodecsWithoutTableLikeResources = new Set< + PgResourceOptionCodec + >(); const walkCodec = ( - codec: PgCodec, + codec: PgResourceOptionCodec, isAccessibleViaAttribute = false, - seen = new Set(), + seen = new Set>(), ) => { if (seen.has(codec)) { return; @@ -1122,12 +1173,16 @@ export function makeRegistry< }; // Add table-like codecs used within attributes - for (const codec of Object.values(registry.pgCodecs)) { + for (const codec of Object.values(registry.pgCodecs) as Array< + PgResourceOptionCodec + >) { walkCodec(codec); } // Remove from these those codecs that already have resources - for (const resource of Object.values(registry.pgResources)) { + for (const resource of Object.values(registry.pgResources) as Array< + TResourceOptions[number] + >) { if (!resource.parameters) { tableLikeCodecsWithoutTableLikeResources.delete(resource.codec); } @@ -1136,12 +1191,21 @@ 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 resource = new PgResource(registry, { + const resourceName = `frmcdc_${codec.name}` as PgResourceOptionName< + TResourceOptions[number] + >; + const resource = new PgResource< + PgResourceOptionName, + PgResourceOptionCodec, + PgResourceOptionUniques, + PgResourceOptionParameters, + TCodecs, + TResourceOptions, + TRelationConfigs + >(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,13 +1214,15 @@ export function makeRegistry< behavior: "-*", }, }, - }) as any; + }); Object.defineProperties(resource, { $exporter$args: { value: [registry, resourceName] }, $exporter$factory: { - value: (registry: PgRegistry, resourceName: string) => - registry.pgResources[resourceName], + value: ( + registry: PgRegistry, + resourceName: PgResourceOptionName, + ) => registry.pgResources[resourceName], }, }); @@ -1164,9 +1230,9 @@ export function makeRegistry< } } - for (const codecName of Object.keys( - config.pgRelations, - ) as (keyof typeof config.pgRelations)[]) { + for (const codecName of Object.keys(config.pgRelations) as Array< + PgCodecName> + >) { const relations = config.pgRelations[codecName]; if (!relations) { continue; @@ -1183,31 +1249,41 @@ export function makeRegistry< }, }); - 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) as Array< + PgCodecRelationConfigName + >) { + 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, - codecName: string, - relationName: string, + registry: PgRegistry, + codecName: keyof PgRegistry< + TCodecs, + TResourceOptions, + TRelationConfigs + >["pgRelations"], + relationName: keyof PgRegistry< + TCodecs, + TResourceOptions, + TRelationConfigs + >["pgRelations"][keyof PgRegistry< + TCodecs, + TResourceOptions, + TRelationConfigs + >["pgRelations"]], ) => registry.pgRelations[codecName][relationName], }, }); @@ -1215,7 +1291,13 @@ export function makeRegistry< builtRelations[relationName] = builtRelation; } - registry.pgRelations[codecName] = builtRelations; + registry.pgRelations[ + codecName as unknown as keyof PgRegistry< + TCodecs, + TResourceOptions, + TRelationConfigs + >["pgRelations"] + ] = builtRelations; } validateRelations(registry); @@ -1224,14 +1306,20 @@ export function makeRegistry< } exportAs("@dataplan/pg", makeRegistry, "makeRegistry"); -function validateRelations(registry: PgRegistry): void { +function validateRelations< + TCodecs extends ReadonlyArray, + TResourceOptions extends ReadonlyArray, + TRelationConfigs extends ReadonlyArray, +>(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]) => { const { via, identicalVia } = col; @@ -1354,10 +1442,7 @@ export function makeRegistryBuilder(): PgRegistryBuilder<{}, {}, {}> { localCodec, remoteResourceOptions, ...relation, - } as PgCodecRelationConfig< - PgCodecWithAttributes, - PgResourceOptions - >; + }; return builder; }, diff --git a/grafast/dataplan-pg/src/examples/exampleSchema.ts b/grafast/dataplan-pg/src/examples/exampleSchema.ts index 742f209105..5f6a7705dd 100644 --- a/grafast/dataplan-pg/src/examples/exampleSchema.ts +++ b/grafast/dataplan-pg/src/examples/exampleSchema.ts @@ -107,7 +107,11 @@ import { recordCodec, TYPES, } from "../index.js"; -import type { GetPgResourceAttributes, PgCodec } from "../interfaces"; +import type { + AnyPgCodec, + GetPgResourceAttributes, + PgCodec, +} from "../interfaces"; import { PgPageInfoStep } from "../steps/pgPageInfo.js"; import type { PgPolymorphicTypeMap } from "../steps/pgPolymorphic.js"; import type { PgSelectParsedCursorStep } from "../steps/pgSelect.js"; @@ -196,20 +200,28 @@ export function makeExampleSchema( sqlFromArgDigests, ) => { const col = < + const TName extends string, + const TNotNull extends boolean, TOptions extends { - codec: PgCodec; - notNull?: boolean; - expression?: PgCodecAttribute["expression"]; + name: TName; + codec: AnyPgCodec; + notNull?: TNotNull; + expression?: PgCodecAttribute["expression"]; via?: PgCodecAttributeVia; identicalVia?: PgCodecAttributeVia; }, >( options: TOptions, - ): PgCodecAttribute => { - const { notNull, codec, expression, via, identicalVia } = options; + ): PgCodecAttribute< + TOptions extends { name: infer U } ? U : never, + TOptions extends { codec: infer U } ? U : never, + TNotNull extends undefined ? false : TNotNull + > => { + const { name, notNull, codec, expression, via, identicalVia } = options; return { + name: name as TOptions extends { name: infer U } ? U : never, codec: codec as TOptions extends { codec: infer U } ? U : never, - notNull: !!notNull, + notNull: !!notNull as any, expression, via, identicalVia, @@ -221,10 +233,11 @@ export function makeExampleSchema( name: "forums", identifier: sql`app_public.forums`, attributes: { - id: col({ notNull: true, codec: TYPES.uuid }), - name: col({ notNull: true, codec: TYPES.citext }), - archived_at: col({ codec: TYPES.timestamptz }), + id: col({ name: "id", notNull: true, codec: TYPES.uuid }), + name: col({ name: "name", notNull: true, codec: TYPES.citext }), + archived_at: col({ name: "archived_at", codec: TYPES.timestamptz }), is_archived: col({ + name: "is_archived", codec: TYPES.boolean, expression: (alias) => sql`${alias}.archived_at is not null`, }), @@ -3157,7 +3170,15 @@ export function makeExampleSchema( any, any, PgSelectSingleStep< - PgResource + PgResource< + any, + typeof singleTableItemsResource.codec, + any, + any, + any, + any, + any + > >, any, any diff --git a/grafast/dataplan-pg/src/interfaces.ts b/grafast/dataplan-pg/src/interfaces.ts index bc100e469e..4641536105 100644 --- a/grafast/dataplan-pg/src/interfaces.ts +++ b/grafast/dataplan-pg/src/interfaces.ts @@ -2,10 +2,17 @@ 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, PgCodecAttributeName } from "./codecs.js"; import type { + AnyPgResource, + AnyPgResourceOptions, PgCodecRefs, PgResource, + PgResourceCodec, + PgResourceOptionCodec, + PgResourceOptionName, + PgResourceOptionParameters, + PgResourceOptionUniques, PgResourceOptions, PgResourceParameter, PgResourceUnique, @@ -20,13 +27,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 +122,65 @@ export type PgCodecPolymorphism = | PgCodecPolymorphismRelational | PgCodecPolymorphismUnion; +export interface PgRangeItemCodec + extends PgCodec {} + +export interface AnyPgCodec + 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 + : never; +export type PgCodecFromPostgres = U extends PgCodec< + any, + any, + infer TFromPostgres, + any, + any, + any, + any +> + ? TFromPostgres + : never; + +export type PgCodecAttributesRecord< + TCodecAttributes extends ReadonlyArray, +> = { + [TCodecAttribute in TCodecAttributes[number] as PgCodecAttributeName]: TCodecAttribute; +}; /** * 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 ReadonlyArray, + TFromPostgres, + TFromJavaScript, + TArrayItemCodec extends AnyPgCodec, + TDomainItemCodec extends AnyPgCodec, + TRangeItemCodec extends PgRangeItemCodec, > { /** * Unique name to identify this codec. @@ -197,7 +239,7 @@ export interface PgCodec< /** * If this is a composite type, the attributes it supports. */ - attributes: TAttributes; + attributes?: PgCodecAttributesRecord; /** * A callback to return `'true'` (text string) if the composite type @@ -264,15 +306,15 @@ export interface PgCodec< } export type PgCodecWithAttributes< - TAttributes extends PgCodecAttributes = PgCodecAttributes, -> = PgCodec; + TAttributes extends ReadonlyArray, +> = PgCodec; export type PgCodecAnyScalar = PgCodec< string, - undefined, + never, any, any, - undefined, + never, any, any >; @@ -287,7 +329,7 @@ export type PgCodecList< any, any >, -> = PgCodec; +> = PgCodec; export type PgEnumValue = { value: TValue; @@ -300,15 +342,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 +350,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; } @@ -345,9 +379,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,8 +403,8 @@ export interface PgGroupSpec { } export type TuplePlanMap< - TAttributes extends PgCodecAttributes, - TTuple extends ReadonlyArray, + TAttributes extends ReadonlyArray, + TTuple extends ReadonlyArray>, > = { [Index in keyof TTuple]: { // Optional attributes @@ -395,18 +429,13 @@ export type TuplePlanMap< * to. */ export type PlanByUniques< - TAttributes extends PgCodecAttributes, + TAttributes extends ReadonlyArray, TUniqueAttributes extends ReadonlyArray>, -> = TAttributes extends PgCodecAttributes - ? TuplePlanMap< - TAttributes, - TUniqueAttributes[number]["attributes"] & string[] - >[number] - : undefined; +> = 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 +552,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 +565,7 @@ export interface PgCodecRelationBase< /** * The attributes locally used in this relationship. */ - localAttributes: readonly (keyof TLocalCodec["attributes"])[]; + localAttributes: Array[number]>>; /** * The remote attributes that are joined against. @@ -561,24 +592,37 @@ export interface PgCodecRelationBase< description?: string; } +export type AnyPgCodecRelationConfig = 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, + TName extends string = string, + TLocalCodec extends AnyPgCodec = PgCodecWithAttributes, TRemoteResourceOptions extends PgResourceOptions = PgResourceOptions< any, - PgCodecWithAttributes, + PgCodecWithAttributes, any, any >, > extends PgCodecRelationBase< + TName, TLocalCodec, - TRemoteResourceOptions extends PgResourceOptions< - any, - PgCodec, - any, - any + PgCodecAttributeName< + PgCodecAttributes>[number] > - ? keyof UAttributes - : never > { remoteResourceOptions: TRemoteResourceOptions; } @@ -587,25 +631,23 @@ export interface PgCodecRelationConfig< * Describes a relation from a codec to a resource */ export interface PgCodecRelation< - TLocalCodec extends PgCodecWithAttributes = PgCodecWithAttributes, + TName extends string = string, + TLocalCodec extends PgCodecWithAttributes = PgCodecWithAttributes, TRemoteResource extends PgResource< any, - PgCodecWithAttributes, + PgCodecWithAttributes, + any, + any, any, any, any - > = PgResource, + > = PgResource, any, any, any, any, any>, > extends PgCodecRelationBase< + TName, TLocalCodec, - TRemoteResource extends PgResource< - any, - PgCodec, - any, - any, - any + PgCodecAttributeName< + PgCodecAttributes>[number] > - ? keyof UAttributes - : never > { /** * The remote resource this relation relates to. @@ -614,37 +656,13 @@ export interface PgCodecRelation< } 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 ReadonlyArray, + TResourceOptions extends ReadonlyArray, + TRelationConfigs extends ReadonlyArray, > { - pgCodecs: TCodecs; - pgResources: TResourceOptions; - pgRelations: TRelations; + pgCodecs: PgRegistryCodecs; + pgResources: PgRegistryResourceOptions; + pgRelations: PgRegistryRelationConfigs; } // https://github.com/microsoft/TypeScript/issues/47980#issuecomment-1049304607 @@ -652,150 +670,134 @@ 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< - string, - PgCodec - >, - TResourceOptions extends { - [name in string]: PgResourceOptions< - name, - PgCodec, // TCodecs[keyof TCodecs], - ReadonlyArray>, - readonly PgResourceParameter[] | undefined - >; - } = 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; +export type PgRegistryCodecs> = { + [TCodec in TCodecs[number] as PgCodecName]: TCodec; +}; + +export type PgRegistryResourceOptions< + TResourceOptions extends ReadonlyArray, +> = { + [TResourceOption in TResourceOptions[number] as PgResourceOptionName]: TResourceOption; +}; + +export type PgRegistryResources< + TCodecs extends ReadonlyArray, + TResourceOptions extends ReadonlyArray, + TRelationConfigs extends ReadonlyArray, +> = { + [TResourceOption in TResourceOptions[number] as PgResourceOptionName]: PgResource< + PgResourceOptionName, + PgResourceOptionCodec, + PgResourceOptionUniques, + PgResourceOptionParameters, + TCodecs, + TResourceOptions, + TRelationConfigs + >; +}; +export type PgRegistryRelationConfigs< + TRelationConfigs extends ReadonlyArray, +> = { + [TRelationConfig in TRelationConfigs[number] as PgCodecName< + PgCodecRelationConfigLocalCodec + >]: { + [TRelationName in PgCodecRelationConfigName]: TRelationConfig; }; - 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 PgRelation< + TName extends string, + TLocalCodec extends AnyPgCodec, + TRemoteResourceOptions extends AnyPgResourceOptions, + TCodecs extends ReadonlyArray, + TResourceOptions extends ReadonlyArray, + TRelationConfigs extends ReadonlyArray, +> extends Omit< + PgCodecRelationConfig, + "remoteResourceOptions" + > { + remoteResource: PgResource< + PgResourceOptionName, + PgResourceOptionCodec, + PgResourceOptionUniques, + PgResourceOptionParameters, + TCodecs, + TResourceOptions, + TRelationConfigs + >; +} + +export type PgRegistryRelations< + TCodecs extends ReadonlyArray, + TResourceOptions extends ReadonlyArray, + TRelationConfigs extends ReadonlyArray, +> = { + [TRelationConfig in TRelationConfigs[number] as PgCodecName< + PgCodecRelationConfigLocalCodec + >]: { + [TRelationName in PgCodecRelationConfigName]: PgRelation< + TRelationName, + PgCodecRelationConfigLocalCodec, + PgCodecRelationConfigRemoteResourceOptions, + TCodecs, + TResourceOptions, + TRelationConfigs + >; }; +}; + +export interface PgRegistry< + TCodecs extends ReadonlyArray, + TResourceOptions extends ReadonlyArray, + TRelationConfigs extends ReadonlyArray, +> { + pgCodecs: PgRegistryCodecs; + pgResources: PgRegistryResources; + pgRelations: PgRegistryRelations; } export type GetPgRegistryCodecs> = TRegistry["pgCodecs"]; +export type GetPgRegistryRelations = U extends PgRegistry< + infer TCodecs, + infer TResourceOptions, + infer TRelationConfigs +> + ? PgRegistryRelations + : never; + export type GetPgRegistrySources> = TRegistry["pgResources"]; export type GetPgRegistryCodecRelations< TRegistry extends PgRegistry, TCodec extends PgCodec, -> = TRegistry["pgRelations"][TCodec["name"]]; +> = GetPgRegistryRelations[PgCodecName]; -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"]; +export type GetPgCodecAttributes = TCodec extends PgCodec< + any, + infer UAttributes, + any, + any, + any, + any, + any +> + ? UAttributes + : never; + +export type GetPgResourceRegistry = + TResource["registry"]; + +export type GetPgResourceCodec = + TResource["codec"]; + +export type GetPgResourceAttributes = + GetPgCodecAttributes; + +export type GetPgResourceRelations = + TResource["registry"]["pgRelations"][TResource["codec"]["name"]]; + +export type GetPgResourceUniques = + TResource["uniques"]; diff --git a/grafast/dataplan-pg/src/steps/pgSelectSingle.ts b/grafast/dataplan-pg/src/steps/pgSelectSingle.ts index 6e2f4b454a..2575811d73 100644 --- a/grafast/dataplan-pg/src/steps/pgSelectSingle.ts +++ b/grafast/dataplan-pg/src/steps/pgSelectSingle.ts @@ -597,7 +597,7 @@ export function pgSelectFromRecord< PgCodec, any, any, - PgRegistry + PgRegistry >, >( resource: TResource,