// Query // ----- import type { EntitiesDef, IContainEntitiesAndLinks, InstantGraph, LinkAttrDef, RuleParams, ResolveAttrs, ResolveEntityAttrs, DataAttrDef, AttrsDefs, EntityDefFromSchema, InstantUnknownSchemaDef, } from './schemaTypes.ts'; type BuiltIn = Date | Function | Error | RegExp; type Primitive = string | number | boolean | symbol | null | undefined; export type Expand = T extends BuiltIn | Primitive ? T : T extends object ? T extends infer O ? { [K in keyof O]: Expand } : never : T; // NonEmpty disallows {}, so that you must provide at least one field export type NonEmpty = { [K in keyof T]-?: Required>; }[keyof T]; type BaseWhereClauseValueComplex = { /** @deprecated use `$in` instead of `in` */ in?: V[]; $in?: V[]; /** @deprecated use `$ne` instead of `not` */ $not?: V; $ne?: V; $gt?: V; $lt?: V; $gte?: V; $lte?: V; }; type IsAny = boolean extends (T extends never ? true : false) ? true : false; type WhereClauseValueComplex = BaseWhereClauseValueComplex & (IsAny extends true ? { $ilike?: string; $like?: string; $isNull?: boolean; } : (V extends string ? { $like?: string; } : {}) & (R extends false ? { $isNull?: boolean; } : {}) & (I extends true ? { $ilike?: string; } : {})); // Make type display better type WhereClauseValue< D extends DataAttrDef, > = D extends DataAttrDef ? | (IsAny extends true ? string | number | boolean : V) | NonEmpty> : never; type WhereClauseColumnEntries< T extends { [key: string]: DataAttrDef; }, > = { [key in keyof T]?: WhereClauseValue; }; type WhereClauseComboEntries< T extends Record>, > = { or?: | WhereClauses[] | WhereClauseValue>; and?: | WhereClauses[] | WhereClauseValue>; }; type WhereClauses< T extends Record>, > = ( | WhereClauseComboEntries | (WhereClauseComboEntries & WhereClauseColumnEntries) ) & { id?: WhereClauseValue>; [key: `${string}.${string}`]: WhereClauseValue< DataAttrDef >; }; /** * A tuple representing a cursor. * These should not be constructed manually. The current format * is an implementation detail that may change in the future. * Use the `endCursor` or `startCursor` from the PageInfoResponse as the * `before` or `after` field in the query options. */ type Cursor = [string, string, any, number]; type Direction = 'asc' | 'desc'; type IndexedKeys = { [K in keyof Attrs]: Attrs[K] extends DataAttrDef< any, any, infer IsIndexed, boolean > ? IsIndexed extends true ? K : never : never; }[keyof Attrs]; export type Order< Schema extends IContainEntitiesAndLinks, EntityName extends keyof Schema['entities'], > = IndexedKeys extends never ? { serverCreatedAt?: Direction; } : { [K in IndexedKeys]?: Direction; } & { serverCreatedAt?: Direction; }; type $Option< S extends IContainEntitiesAndLinks, K extends keyof S['entities'], > = { $?: { where?: WhereClauses>; order?: Order; limit?: number; last?: number; first?: number; offset?: number; after?: Cursor; afterInclusive?: boolean; before?: Cursor; beforeInclusive?: boolean; fields?: InstaQLFields; }; }; type NamespaceVal = | $Option, keyof EntitiesDef> | ($Option, keyof EntitiesDef> & Subquery); type Subquery = { [namespace: string]: NamespaceVal }; interface Query { [namespace: string]: NamespaceVal; } type InstantObject = { id: string; [prop: string]: any; }; type ResponseObject = K extends keyof Schema ? { id: string } & Schema[K] : InstantObject; type IsEmptyObject = T extends Record ? true : false; type ResponseOf = { [K in keyof Q]: IsEmptyObject extends true ? ResponseObject[] : (ResponseOf & ResponseObject)[]; }; type Remove$ = T extends object ? { [K in keyof T as Exclude]: Remove$ } : T; type Remove$NonRecursive = T extends object ? { [K in keyof T as Exclude]: T[K] } : T; type QueryResponse< Q, Schema, WithCardinalityInference extends boolean = false, UseDates extends boolean = false, > = Schema extends InstantGraph ? InstaQLQueryResult : ResponseOf<{ [K in keyof Q]: Remove$ }, Schema>; type InstaQLResponse = Schema extends IContainEntitiesAndLinks ? Q extends InstaQLParams | undefined ? InstaQLResult : never : never; type PageInfoResponse = { [K in keyof T]: { startCursor: Cursor; endCursor: Cursor; hasNextPage: boolean; hasPreviousPage: boolean; }; }; /** * (XXX) * https://github.com/microsoft/TypeScript/issues/26051 * * TypeScript can permit extra keys when a generic extends a type. * * For some reason, it makes it possible to write a query like so: * * dummyQuery({ * users: { * $: { where: { "foo": 1 } }, * posts: { * $: { "far": {} } * } * } * * The problem: $: { "far": {} } * * This passes, when it should in reality fail. I don't know why * adding `Exactly` fixes this, but it does. * * */ type Exactly = Parent & { [K in keyof Child]: K extends keyof Parent ? Child[K] : never; }; // SafeLookup is like doing // T['A']['B'][number], but it will merge with undefined if any // of the intermediates are undefined type SafeLookup = K extends [ infer First, ...infer Rest extends readonly PropertyKey[], ] ? First extends keyof NonNullable ? | SafeLookup[First], Rest> | (T extends null | undefined ? undefined : never) : undefined : T; // ========== // InstaQL helpers type InstaQLEntitySubqueryResult< Schema extends IContainEntitiesAndLinks, EntityName extends keyof Schema['entities'], Query extends InstaQLEntitySubquery | undefined = {}, UseDates extends boolean = false, > = { [QueryPropName in keyof Query]: Schema['entities'][EntityName]['links'][QueryPropName] extends LinkAttrDef< infer Cardinality, infer LinkedEntityName > ? LinkedEntityName extends keyof Schema['entities'] ? Cardinality extends 'one' ? | InstaQLEntity< Schema, LinkedEntityName, Remove$NonRecursive>, SafeLookup, UseDates > | undefined : InstaQLEntity< Schema, LinkedEntityName, Remove$NonRecursive>, SafeLookup, UseDates >[] : never : never; }; type InstaQLQueryEntityLinksResult< Entities extends EntitiesDef, EntityName extends keyof Entities, Query extends { [LinkAttrName in keyof Entities[EntityName]['links']]?: any; }, WithCardinalityInference extends boolean, UseDates extends boolean = false, > = { [QueryPropName in keyof Query]: Entities[EntityName]['links'][QueryPropName] extends LinkAttrDef< infer Cardinality, infer LinkedEntityName > ? LinkedEntityName extends keyof Entities ? WithCardinalityInference extends true ? Cardinality extends 'one' ? | InstaQLQueryEntityResult< Entities, LinkedEntityName, Query[QueryPropName], WithCardinalityInference, UseDates > | undefined : InstaQLQueryEntityResult< Entities, LinkedEntityName, Query[QueryPropName], WithCardinalityInference, UseDates >[] : InstaQLQueryEntityResult< Entities, LinkedEntityName, Query[QueryPropName], WithCardinalityInference, UseDates >[] : never : never; }; // Pick, but applies the pick to each union type DistributePick = T extends any ? { [P in K]: P extends keyof T ? T[P] : never } : never; type InstaQLFields< S extends IContainEntitiesAndLinks, K extends keyof S['entities'], > = (Extract, string> | 'id')[]; type ComputeAttrs< AllAttrs, Fields extends readonly string[] | undefined, > = Fields extends readonly string[] ? DistributePick> : AllAttrs; type InstaQLEntity< Schema extends IContainEntitiesAndLinks, EntityName extends keyof Schema['entities'], Subquery extends InstaQLEntitySubquery | undefined = {}, Fields extends InstaQLFields | undefined = undefined, UseDates extends boolean = false, > = Expand< { id: string } & ComputeAttrs< ResolveEntityAttrs, Fields > & InstaQLEntitySubqueryResult >; export type InstaQLQueryEntityResult< Entities extends EntitiesDef, EntityName extends keyof Entities, Query extends { [QueryPropName in keyof Entities[EntityName]['links']]?: any; }, WithCardinalityInference extends boolean, UseDates extends boolean, > = { id: string } & ResolveAttrs & InstaQLQueryEntityLinksResult< Entities, EntityName, Query, WithCardinalityInference, UseDates >; type InstaQLQueryResult< Entities extends EntitiesDef, Query, WithCardinalityInference extends boolean, UseDates extends boolean, > = { [QueryPropName in keyof Query]: QueryPropName extends keyof Entities ? InstaQLQueryEntityResult< Entities, QueryPropName, Query[QueryPropName], WithCardinalityInference, UseDates >[] : never; }; type InstaQLResult< Schema extends IContainEntitiesAndLinks, Query extends InstaQLParams | undefined, UseDates extends boolean = false, > = Expand<{ [QueryPropName in keyof Query]: QueryPropName extends keyof Schema['entities'] ? InstaQLEntity< Schema, QueryPropName, Remove$NonRecursive>, SafeLookup, UseDates >[] : never; }>; type InstaQLEntitySubquery< Schema extends IContainEntitiesAndLinks, EntityName extends keyof Schema['entities'], > = { [QueryPropName in keyof Schema['entities'][EntityName]['links']]?: | $Option | ($Option & InstaQLEntitySubquery< Schema, Schema['entities'][EntityName]['links'][QueryPropName]['entityName'] >); }; type InstaQLQuerySubqueryParams< S extends IContainEntitiesAndLinks, E extends keyof S['entities'], > = { [K in keyof S['entities'][E]['links']]?: | $Option | ($Option & InstaQLQuerySubqueryParams< S, S['entities'][E]['links'][K]['entityName'] >); }; type InstaQLParams> = { [K in keyof S['entities']]?: | $Option | ($Option & InstaQLQuerySubqueryParams); }; /** * @deprecated * `InstaQLQueryParams` is deprecated. Use `InstaQLParams` instead. * * @example * // Before * const myQuery = {...} satisfies InstaQLQueryParams * // After * const myQuery = {...} satisfies InstaQLParams */ type InstaQLQueryParams> = InstaQLParams; type InstaQLOptions = { ruleParams: RuleParams; }; // Start of new types type ValidQueryObject< T extends Record, Schema extends IContainEntitiesAndLinks, EntityName extends keyof Schema['entities'], TopLevel extends boolean, > = keyof T extends keyof Schema['entities'][EntityName]['links'] | '$' ? { [K in keyof Schema['entities'][EntityName]['links']]?: ValidQueryObject< T[K], Schema, Schema['entities'][EntityName]['links'][K]['entityName'], false >; } & { $?: ValidDollarSignQuery; } : never; type PaginationKeys = | 'limit' | 'last' | 'first' | 'offset' | 'after' | 'afterInclusive' | 'before' | 'beforeInclusive'; type AllowedDollarSignKeys = TopLevel extends true ? PaginationKeys | 'where' | 'fields' | 'order' : 'where' | 'fields' | 'order' | 'limit'; type ValidFieldNames< S extends IContainEntitiesAndLinks, EntityName extends keyof S['entities'], > = Extract, string> | 'id'; type ValidDollarSignQuery< Input extends { [key: string]: any }, Schema extends IContainEntitiesAndLinks, EntityName extends keyof Schema['entities'], TopLevel extends boolean, > = keyof Input extends AllowedDollarSignKeys ? { where?: ValidWhereObject; fields?: ValidFieldNames[]; order?: Order; limit?: number; } & (TopLevel extends true ? { last?: number; first?: number; offset?: number; after?: Cursor; afterInclusive?: boolean; before?: Cursor; beforeInclusive?: boolean; } : {}) : never; type StringifiableKey = Extract; type ValidWhereNestedPath< T, K extends string | number | symbol, Schema extends IContainEntitiesAndLinks, > = T extends object ? K extends keyof T ? K // Allow link names as valid paths (they'll default to id) : K extends `${infer K0}.${infer KR}` ? K0 extends keyof T ? T[K0] extends keyof Schema['entities'] ? `${K0}.${ | ValidWhereNestedPath< { [K in keyof Schema['entities'][T[K0]]['links']]: Schema['entities'][T[K0]]['links'][K]['entityName']; }, KR, Schema > | StringifiableKey | 'id'}` : string & keyof T : string & keyof T : string & keyof T : never; type ValidDotPath< Input extends string | number | symbol, Schema extends IContainEntitiesAndLinks, EntityName extends keyof Schema['entities'], > = ValidWhereNestedPath< { [K in keyof Schema['entities'][EntityName]['links']]: Schema['entities'][EntityName]['links'][K]['entityName']; }, Input, Schema >; type WhereOperatorObject = keyof Input extends | keyof BaseWhereClauseValueComplex | '$ilike' | '$like' | '$isNull' ? BaseWhereClauseValueComplex & (IsAny extends true ? { $ilike?: string; $like?: string; $isNull?: boolean; } : (V extends string ? { $like?: string; } : {}) & (R extends false ? { $isNull?: boolean; } : {}) & (I extends true ? { $ilike?: string; } : {})) : never; type ValidWhereValue< Input, AttrDef extends DataAttrDef, > = AttrDef extends DataAttrDef ? Input extends V ? V : NonEmpty> : never; type NoDistribute = [T] extends [any] ? T : never; type ValidWhereObject< Input extends { [key: string]: any } | undefined, Schema extends IContainEntitiesAndLinks, EntityName extends keyof Schema['entities'], > = Input extends undefined ? undefined : keyof Input extends | ValidDotPath | keyof Schema['entities'][EntityName]['attrs'] | 'and' | 'or' | 'id' ? { [K in ValidDotPath]?: ValidWhereValue< Input[K], ExtractAttrFromDotPath >; } & { [K in keyof Schema['entities'][EntityName]['attrs']]?: ValidWhereValue< Input[K], Schema['entities'][EntityName]['attrs'][K] >; } & { and?: Input extends { and: Array; } ? ValidWhereObject, Schema, EntityName>[] : never; or?: Input extends { or: Array; } ? ValidWhereObject, Schema, EntityName>[] : never; } & { // Special case for id id?: ValidWhereValue< SafeLookup, DataAttrDef >; } : never; /** * Extracts the attribute definition from a valid dot path. * Given a dot path like "user.posts.title", this will resolve to the type of the "title" attribute. * If the path is just a link name (e.g., "posts"), it defaults to the id field. * Returns DataAttrDef or never */ type ExtractAttrFromDotPath< Path extends string | number | symbol, Schema extends IContainEntitiesAndLinks, EntityName extends keyof Schema['entities'], > = Path extends keyof Schema['entities'][EntityName]['attrs'] ? Schema['entities'][EntityName]['attrs'][Path] : Path extends 'id' ? DataAttrDef : Path extends `${infer LinkName}.${infer RestPath}` ? LinkName extends keyof Schema['entities'][EntityName]['links'] ? ExtractAttrFromDotPath< RestPath, Schema, Schema['entities'][EntityName]['links'][LinkName]['entityName'] > : never : Path extends keyof Schema['entities'][EntityName]['links'] ? DataAttrDef : never; type ValidQuery< Q extends Record, S extends IContainEntitiesAndLinks, > = S extends InstantUnknownSchemaDef ? InstaQLParams : keyof Q extends keyof S['entities'] ? { [K in keyof S['entities']]?: ValidQueryObject; } : never; export { Query, QueryResponse, InstaQLResponse, PageInfoResponse, InstantObject, Exactly, Remove$, ValidQuery, InstaQLQueryResult, InstaQLParams, InstaQLOptions, InstaQLEntitySubquery, InstaQLEntity, InstaQLResult, InstaQLFields, Cursor, InstaQLQueryParams, };