import { CollectionNameFromModels, Models } from '../../schema/types/models.js'; import { StringKey, Unalias } from '../../utils/types.js'; import { Decoded, ModelPaths, ModelRelationshipPaths, PathFiltered, ResolveRelationshipPath, SchemaPaths, } from '../../schema/index.js'; /** * All possible queries for a schema, keyed by collection name. */ export type SchemaQueries = Models> = { [CN in CollectionNameFromModels]: CollectionQuery; }; /** * A selection of queries for a schema, returning a union if there are multiple matches */ export type SchemaQuery< M extends Models = Models, CN extends CollectionNameFromModels = CollectionNameFromModels, > = SchemaQueries[CN]; /** * A prepared query, which is used internally by the query engine */ export interface PreparedQuery { collectionName: string; select?: string[]; where?: PreparedWhere; order?: PreparedOrder; limit?: number; after?: QueryAfter; include?: PreparedInclusions; } /** * A user defined query on the database, which may reference the schema or other database objects. */ export interface CollectionQuery< M extends Models = Models, CN extends CollectionNameFromModels = CollectionNameFromModels, > { collectionName: CN; select?: QuerySelection[]; where?: QueryWhere; order?: QueryOrder; limit?: number; after?: QueryAfter; vars?: Record; include?: QueryInclusions; } /** * Cardinality of a query result: * - 'one' - a single result * - 'many' - multiple results */ export type QueryResultCardinality = 'one' | 'many'; export type QuerySelectionFromQuery> = Q extends CollectionQuery ? QuerySelection : never; export type QuerySelection< M extends Models = Models, CN extends CollectionNameFromModels = CollectionNameFromModels, > = SchemaPaths; // ====== Filter Types ====== /** * A query filter, which is a collection of many filters. */ export type QueryWhere< M extends Models = Models, CN extends CollectionNameFromModels = CollectionNameFromModels, > = WhereFilter[]; /** * A single filter, which may have various structures. */ // I've done this with ExistsFilter, but adding a 'type' property to each type for narrowing would be helpful. Should still support old props for backwards compatibility. export type WhereFilter< M extends Models = Models, CN extends CollectionNameFromModels = CollectionNameFromModels, > = // TODO: investigate usage of ModelFilterStatements (may improve type completion) | FilterStatement | FilterGroup | SubQueryFilter | RelationshipExistsFilter | boolean; export type ModelFilterStatements< M extends Models = Models, CN extends CollectionNameFromModels = CollectionNameFromModels, > = { [K in ModelPaths]: FilterStatement; }; export type ModelFilterStatement< M extends Models = Models, CN extends CollectionNameFromModels = CollectionNameFromModels, K extends ModelPaths = ModelPaths, > = ModelFilterStatements[K]; /** * A single filter statement of the shape [path, operator, value]. */ export type FilterStatement< M extends Models = Models, CN extends CollectionNameFromModels = CollectionNameFromModels, K extends ModelPaths = ModelPaths, > = readonly [ K, //Operations> I think typescript has trouble inferring this because its a tuple, seeing a union of all ops, which is fine for now // However, it also causes small issues with the type system with ex. try S.RelationMany('users', { where: [ or([ ['id', '=', '$liked_by_ids'], or([['id', '=', '$liked_by_ids']]), ]), ], }) // It (i think) doesn't recognize string in ['id', string, string] as a valid operator and is failing string, any, ]; export type PreparedWhere = PreparedWhereFilter[]; export type PreparedWhereFilter = | FilterStatement | PreparedFilterGroup | PreparedSubQueryFilter | boolean; export type PreparedFilterGroup = FilterGroup< Models, CollectionNameFromModels, PreparedWhere >; export type PreparedSubQueryFilter = { exists: PreparedQuery; }; /** * A set of filters specified to be combined with AND or OR. */ export type FilterGroup< M extends Models = Models, CN extends CollectionNameFromModels = CollectionNameFromModels, W extends QueryWhere | PreparedWhere = | QueryWhere | PreparedWhere, > = AndFilterGroup | OrFilterGroup; export type QueryFilterGroup< M extends Models = Models, CN extends CollectionNameFromModels = CollectionNameFromModels, > = FilterGroup>; /** * A group of filters combined with AND. */ export type AndFilterGroup< M extends Models = Models, CN extends CollectionNameFromModels = CollectionNameFromModels, W extends QueryWhere | PreparedWhere = | QueryWhere | PreparedWhere, > = { mod: 'and'; filters: W; }; /** * A group of filters combined with OR. */ export type OrFilterGroup< M extends Models = Models, CN extends CollectionNameFromModels = CollectionNameFromModels, W extends QueryWhere | PreparedWhere = | QueryWhere | PreparedWhere, > = { mod: 'or'; filters: W; }; /** * An exists filter that will check if a subquery returns any results. */ export type SubQueryFilter< M extends Models = Models, SQ extends SchemaQuery = SchemaQuery, // This is the collection name of the subquery, not the parent query // SubqueryCN extends CollectionNameFromModels = CollectionNameFromModels, > = { exists: SQ; }; /** * An exists filter that will check if a relationship in the schema returns any results. */ // This may be never if M = any ... might be a place to be more flexible export type RelationshipRef< M extends Models = Models, CN extends CollectionNameFromModels = CollectionNameFromModels, > = StringKey>; export type IsRelationshipRef< M extends Models, CN extends CollectionNameFromModels, Ref extends string, > = Ref extends RelationshipRef ? true : false; // TODO: unify with { exists } export type RelationshipExistsFilter< M extends Models = Models, CN extends CollectionNameFromModels = CollectionNameFromModels, P extends ModelRelationshipPaths = ModelRelationshipPaths, Ext extends RelationshipExistsExtension< M, CN, P > = RelationshipExistsExtension, > = { exists: Ext & { _extends: P; }; }; export type RelationshipExistsExtension< M extends Models = Models, CN extends CollectionNameFromModels = CollectionNameFromModels, P extends ModelRelationshipPaths = ModelRelationshipPaths, > = Pick< SchemaQuery['query']['collectionName']>, 'where' >; // ====== Order Types ====== // ========= Prepared Types ========= export type PreparedOrder = PreparedOrderStatement[]; export type PreparedOrderStatement = OrderStatement | RelationalOrderStatement; // TODO: break this out into unprepared and prepared types // TODO: make this an object or different form that is easier to guard against export type RelationalOrderStatement< M extends Models = Models, CN extends CollectionNameFromModels = CollectionNameFromModels, > = [ property: ModelPaths, direction: 'ASC' | 'DESC', subquery: PreparedRelationSubquery<'one'>, ]; // ========= Unprepared Types ========= /** * A query order, which is a collection of many orders. */ export type QueryOrder< M extends Models = Models, CN extends CollectionNameFromModels = CollectionNameFromModels, > = OrderStatement[]; /** * A single order statement of the shape [path, direction]. */ export type OrderStatement< M extends Models = Models, CN extends CollectionNameFromModels = CollectionNameFromModels, > = [property: ModelPaths, direction: 'ASC' | 'DESC']; // ====== Pagination Types ====== export type QueryAfter = [cursor: ValueCursor, inclusive: boolean]; export type ValueCursor = [ value: QueryValuePrimitive, ...values: QueryValuePrimitive[], ]; // ====== Inclusion Types ====== // ========= Prepared Types ========= /** * A map of prepared inclusions, keyed by alias. */ export type PreparedInclusions = { [K in string]: PreparedInclusion; }; /** * A possible inclusion value in a query. */ export type PreparedInclusion = PreparedRelationSubquery; /** * A subquery definition for a prepared query. */ export type PreparedRelationSubquery< Cardinality extends QueryResultCardinality = QueryResultCardinality, > = { subquery: PreparedQuery; cardinality: Cardinality; }; // ========= Unprepared Types ========= /** * A map of inclusions, keyed by alias. */ export type QueryInclusions< M extends Models = Models, CN extends CollectionNameFromModels = CollectionNameFromModels, > = { // Optimally we could exclude RefShorthand from non-relationship keys, but this is tricky to do with TS [K in string]: QueryInclusion; }; /** * A possible inclusion value in a query. */ export type QueryInclusion< M extends Models = Models, CN extends CollectionNameFromModels = CollectionNameFromModels, > = RefShorthand | RelationSubquery | RefSubquery; /** * A shorthand for including a reference. */ export type RefShorthand = true | null; // ============ Ref Subquery Types ============ export type RefSubquery< M extends Models = Models, CN extends CollectionNameFromModels = CollectionNameFromModels, Ref extends RelationshipRef = RelationshipRef, > = { _extends: Ref; } & RefQueryExtension; /** * An extension of a referential subquery, specifying additional query parameters. */ export type RefQueryExtension< M extends Models = Models, CN extends CollectionNameFromModels = CollectionNameFromModels, Ref extends RelationshipRef = RelationshipRef, > = Pick< SchemaQuery>, 'select' | 'include' | 'limit' | 'where' | 'order' >; export type RefDefinition< M extends Models, CN extends CollectionNameFromModels, Ref extends RelationshipRef, > = NonNullable[Ref]; export type RefQuery< M extends Models, CN extends CollectionNameFromModels, Ref extends RelationshipRef, > = RefDefinition['query']; export type RefCollectionName< M extends Models, CN extends CollectionNameFromModels, Ref extends RelationshipRef, > = RefQuery['collectionName']; // ============ Relational Subquery Types ============ /** * A subquery defining a relationship, specifying the subquery and cardinality of the result. */ export type RelationSubquery< M extends Models = Models, Q extends SchemaQuery = SchemaQuery, Cardinality extends QueryResultCardinality = QueryResultCardinality, > = { subquery: Q; cardinality: Cardinality; }; // OTHER TYPES TODO type QueryValuePrimitive = number | string | boolean | Date | null; /** * Possible values that can be passed into a the query engine by a user */ export type QueryValue = | QueryValuePrimitive | NonNullable[]; export type FetchResult< M extends Models, Q extends SchemaQuery, C extends QueryResultCardinality, > = Unalias>; export type AliasedFetchResult< M extends Models, Q extends SchemaQuery, C extends QueryResultCardinality, > = C extends 'one' ? AliasedQueryResult | null : AliasedQueryResult[]; type AliasedQueryResult, Q extends SchemaQuery> = Q extends CollectionQuery ? (Q['select'] extends ReadonlyArray ? // If we have a selection, use that PathFiltered, S extends string ? S : never> : // Else use the entire schema Decoded) & { // also use inclusions [K in StringKey]: InclusionResult< M, CN, K, // @ts-expect-error Q['include'][K] >; } : never; type InclusionResult< M extends Models, CN extends CollectionNameFromModels, Alias extends string, Inclusion extends QueryInclusion, > = // Inclusion is subquery Inclusion extends RelationSubquery ? AliasedFetchResult : // Inclusion is relation extension Inclusion extends RefSubquery ? AliasedFetchResult< M, RefQuery & Omit, RefDefinition['cardinality'] > : // Inclusion is relation shorthand Inclusion extends RefShorthand ? Alias extends RelationshipRef ? AliasedFetchResult< M, RefDefinition['query'], RefDefinition['cardinality'] > : never : never; export type WithSelection< Q extends CollectionQuery, Selection extends QuerySelectionFromQuery, > = Omit & { select: Selection[]; }; export type WithInclusion< Q extends CollectionQuery, Inclusion extends Q extends CollectionQuery ? QueryInclusions : never, > = Omit & { include: Inclusion; }; export type WithInclusionRaw = Omit & { include: Inclusion; };