@instantdb/core
Version:
Instant's core local abstraction
402 lines (359 loc) • 10.7 kB
text/typescript
// Query
// -----
import type {
EntitiesDef,
IContainEntitiesAndLinks,
InstantGraph,
LinkAttrDef,
RuleParams,
ResolveAttrs,
ResolveEntityAttrs,
DataAttrDef,
AttrsDefs,
} from './schemaTypes.ts';
type BuiltIn = Date | Function | Error | RegExp;
type Primitive = string | number | boolean | symbol | null | undefined;
type Expand<T> = T extends BuiltIn | Primitive
? T
: T extends object
? T extends infer O
? { [K in keyof O]: Expand<O[K]> }
: never
: T;
// NonEmpty disallows {}, so that you must provide at least one field
type NonEmpty<T> = {
[K in keyof T]-?: Required<Pick<T, K>>;
}[keyof T];
type WhereArgs = {
/** @deprecated use `$in` instead of `in` */
in?: (string | number | boolean)[];
$in?: (string | number | boolean)[];
$not?: string | number | boolean;
$isNull?: boolean;
$gt?: string | number | boolean;
$lt?: string | number | boolean;
$gte?: string | number | boolean;
$lte?: string | number | boolean;
$like?: string;
$ilike?: string;
};
type WhereClauseValue = string | number | boolean | NonEmpty<WhereArgs>;
type BaseWhereClause = {
[key: string]: WhereClauseValue;
};
type WhereClauseWithCombination = {
or?: WhereClause[] | WhereClauseValue;
and?: WhereClause[] | WhereClauseValue;
};
type WhereClause =
| WhereClauseWithCombination
| (WhereClauseWithCombination & BaseWhereClause);
/**
* 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<Attrs extends AttrsDefs> = {
[K in keyof Attrs]: Attrs[K] extends DataAttrDef<any, any, infer IsIndexed>
? IsIndexed extends true
? K
: never
: never;
}[keyof Attrs];
type Order<
Schema extends IContainEntitiesAndLinks<any, any>,
EntityName extends keyof Schema['entities'],
> =
IndexedKeys<Schema['entities'][EntityName]['attrs']> extends never
? {
serverCreatedAt?: Direction;
}
: {
[K in IndexedKeys<Schema['entities'][EntityName]['attrs']>]?: Direction;
} & {
serverCreatedAt?: Direction;
};
type $Option<
S extends IContainEntitiesAndLinks<any, any>,
K extends keyof S['entities'],
> = {
$?: {
where?: WhereClause;
order?: Order<S, K>;
limit?: number;
last?: number;
first?: number;
offset?: number;
after?: Cursor;
before?: Cursor;
fields?: InstaQLFields<S, K>;
};
};
type NamespaceVal =
| $Option<IContainEntitiesAndLinks<any, any>, keyof EntitiesDef>
| ($Option<IContainEntitiesAndLinks<any, any>, keyof EntitiesDef> & Subquery);
type Subquery = { [namespace: string]: NamespaceVal };
interface Query {
[namespace: string]: NamespaceVal;
}
type InstantObject = {
id: string;
[prop: string]: any;
};
type ResponseObject<K, Schema> = K extends keyof Schema
? { id: string } & Schema[K]
: InstantObject;
type IsEmptyObject<T> = T extends Record<string, never> ? true : false;
type ResponseOf<Q, Schema> = {
[K in keyof Q]: IsEmptyObject<Q[K]> extends true
? ResponseObject<K, Schema>[]
: (ResponseOf<Q[K], Schema> & ResponseObject<K, Schema>)[];
};
type Remove$<T> = T extends object
? { [K in keyof T as Exclude<K, '$'>]: Remove$<T[K]> }
: T;
type Remove$NonRecursive<T> = T extends object
? { [K in keyof T as Exclude<K, '$'>]: T[K] }
: T;
type QueryResponse<
Q,
Schema,
WithCardinalityInference extends boolean = false,
> =
Schema extends InstantGraph<infer E, any>
? InstaQLQueryResult<E, Q, WithCardinalityInference>
: ResponseOf<{ [K in keyof Q]: Remove$<Q[K]> }, Schema>;
type InstaQLResponse<Schema, Q> =
Schema extends IContainEntitiesAndLinks<any, any>
? InstaQLResult<Schema, Q>
: never;
type PageInfoResponse<T> = {
[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, Child> = Parent & {
[K in keyof Child]: K extends keyof Parent ? Child[K] : never;
};
// ==========
// InstaQL helpers
type InstaQLEntitySubqueryResult<
Schema extends IContainEntitiesAndLinks<EntitiesDef, any>,
EntityName extends keyof Schema['entities'],
Query extends InstaQLEntitySubquery<Schema, EntityName> = {},
> = {
[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<Query[QueryPropName]>,
Query[QueryPropName]['$']['fields']
>
| undefined
: InstaQLEntity<
Schema,
LinkedEntityName,
Remove$NonRecursive<Query[QueryPropName]>,
Query[QueryPropName]['$']['fields']
>[]
: never
: never;
};
type InstaQLQueryEntityLinksResult<
Entities extends EntitiesDef,
EntityName extends keyof Entities,
Query extends {
[LinkAttrName in keyof Entities[EntityName]['links']]?: any;
},
WithCardinalityInference extends boolean,
> = {
[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
>
| undefined
: InstaQLQueryEntityResult<
Entities,
LinkedEntityName,
Query[QueryPropName],
WithCardinalityInference
>[]
: InstaQLQueryEntityResult<
Entities,
LinkedEntityName,
Query[QueryPropName],
WithCardinalityInference
>[]
: never
: never;
};
// Pick, but applies the pick to each union
type DistributePick<T, K extends string> = T extends any
? { [P in K]: P extends keyof T ? T[P] : never }
: never;
type InstaQLFields<
S extends IContainEntitiesAndLinks<any, any>,
K extends keyof S['entities'],
> = (Extract<keyof ResolveEntityAttrs<S['entities'][K]>, string> | 'id')[];
type InstaQLEntity<
Schema extends IContainEntitiesAndLinks<EntitiesDef, any>,
EntityName extends keyof Schema['entities'],
Subquery extends InstaQLEntitySubquery<Schema, EntityName> = {},
Fields extends InstaQLFields<Schema, EntityName> | undefined = undefined,
> = Expand<
{ id: string } & (Extract<Fields[number], string> extends undefined
? ResolveEntityAttrs<Schema['entities'][EntityName]>
: DistributePick<
ResolveEntityAttrs<Schema['entities'][EntityName]>,
Exclude<Fields[number], 'id'>
>) &
InstaQLEntitySubqueryResult<Schema, EntityName, Subquery>
>;
type InstaQLQueryEntityResult<
Entities extends EntitiesDef,
EntityName extends keyof Entities,
Query extends {
[QueryPropName in keyof Entities[EntityName]['links']]?: any;
},
WithCardinalityInference extends boolean,
> = { id: string } & ResolveAttrs<Entities, EntityName> &
InstaQLQueryEntityLinksResult<
Entities,
EntityName,
Query,
WithCardinalityInference
>;
type InstaQLQueryResult<
Entities extends EntitiesDef,
Query,
WithCardinalityInference extends boolean,
> = {
[QueryPropName in keyof Query]: QueryPropName extends keyof Entities
? InstaQLQueryEntityResult<
Entities,
QueryPropName,
Query[QueryPropName],
WithCardinalityInference
>[]
: never;
};
type InstaQLResult<
Schema extends IContainEntitiesAndLinks<EntitiesDef, any>,
Query extends InstaQLParams<Schema>,
> = Expand<{
[QueryPropName in keyof Query]: QueryPropName extends keyof Schema['entities']
? InstaQLEntity<
Schema,
QueryPropName,
Remove$NonRecursive<Query[QueryPropName]>,
Query[QueryPropName]['$']['fields']
>[]
: never;
}>;
type InstaQLEntitySubquery<
Schema extends IContainEntitiesAndLinks<EntitiesDef, any>,
EntityName extends keyof Schema['entities'],
> = {
[QueryPropName in keyof Schema['entities'][EntityName]['links']]?:
| $Option<Schema, EntityName>
| ($Option<Schema, EntityName> &
InstaQLEntitySubquery<
Schema,
Schema['entities'][EntityName]['links'][QueryPropName]['entityName']
>);
};
type InstaQLQuerySubqueryParams<
S extends IContainEntitiesAndLinks<any, any>,
E extends keyof S['entities'],
> = {
[K in keyof S['entities'][E]['links']]?:
| $Option<S, S['entities'][E]['links'][K]['entityName']>
| ($Option<S, S['entities'][E]['links'][K]['entityName']> &
InstaQLQuerySubqueryParams<
S,
S['entities'][E]['links'][K]['entityName']
>);
};
type InstaQLParams<S extends IContainEntitiesAndLinks<any, any>> = {
[K in keyof S['entities']]?:
| $Option<S, K>
| ($Option<S, K> & InstaQLQuerySubqueryParams<S, K>);
};
/**
* @deprecated
* `InstaQLQueryParams` is deprecated. Use `InstaQLParams` instead.
*
* @example
* // Before
* const myQuery = {...} satisfies InstaQLQueryParams<Schema>
* // After
* const myQuery = {...} satisfies InstaQLParams<Schema>
*/
type InstaQLQueryParams<S extends IContainEntitiesAndLinks<any, any>> =
InstaQLParams<S>;
type InstaQLOptions = {
ruleParams: RuleParams;
};
export {
Query,
QueryResponse,
InstaQLResponse,
PageInfoResponse,
InstantObject,
Exactly,
Remove$,
InstaQLQueryResult,
InstaQLParams,
InstaQLOptions,
InstaQLQueryEntityResult,
InstaQLEntity,
InstaQLResult,
InstaQLFields,
Cursor,
InstaQLQueryParams,
};