diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4412210f..167f3b4e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ on: jobs: test: name: Test - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 9e519aa6..0d4a51c6 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -9,7 +9,7 @@ on: jobs: docs: name: Publish docs - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 75794c82..876b09a3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,7 +11,7 @@ on: jobs: release: name: Release - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 diff --git a/jest.config.js b/jest.config.js index bc64026a..9029cc9c 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,5 +1,5 @@ module.exports = { preset: 'ts-jest', testEnvironment: 'node', - collectCoverageFrom: ['src/**/*'], + collectCoverageFrom: ['src/**/*', '!src/types.ts'], } diff --git a/src/PostgrestBuilder.ts b/src/PostgrestBuilder.ts index 86e48794..d7bcc26a 100644 --- a/src/PostgrestBuilder.ts +++ b/src/PostgrestBuilder.ts @@ -8,12 +8,16 @@ import type { CheckMatchingArrayTypes, MergePartialResult, IsValidResultOverride, + ClientServerOptions, } from './types' import PostgrestError from './PostgrestError' import { ContainsNull } from './select-query-parser/types' -export default abstract class PostgrestBuilder - implements +export default abstract class PostgrestBuilder< + ClientOptions extends ClientServerOptions, + Result, + ThrowOnError extends boolean = false +> implements PromiseLike< ThrowOnError extends true ? PostgrestResponseSuccess : PostgrestSingleResponse > @@ -28,7 +32,7 @@ export default abstract class PostgrestBuilder) { + constructor(builder: PostgrestBuilder) { this.method = builder.method this.url = builder.url this.headers = builder.headers @@ -53,9 +57,9 @@ export default abstract class PostgrestBuilder { + throwOnError(): this & PostgrestBuilder { this.shouldThrowOnError = true - return this as this & PostgrestBuilder + return this as this & PostgrestBuilder } /** @@ -224,9 +228,14 @@ export default abstract class PostgrestBuilder() method at the end of your call chain instead */ - returns(): PostgrestBuilder, ThrowOnError> { + returns(): PostgrestBuilder< + ClientOptions, + CheckMatchingArrayTypes, + ThrowOnError + > { /* istanbul ignore next */ return this as unknown as PostgrestBuilder< + ClientOptions, CheckMatchingArrayTypes, ThrowOnError > @@ -258,6 +267,7 @@ export default abstract class PostgrestBuilder(): PostgrestBuilder< + ClientOptions, IsValidResultOverride extends true ? // Preserve the optionality of the result if the overriden type is an object (case of chaining with `maybeSingle`) ContainsNull extends true @@ -267,6 +277,7 @@ export default abstract class PostgrestBuilder { return this as unknown as PostgrestBuilder< + ClientOptions, IsValidResultOverride extends true ? // Preserve the optionality of the result if the overriden type is an object (case of chaining with `maybeSingle`) ContainsNull extends true diff --git a/src/PostgrestClient.ts b/src/PostgrestClient.ts index 8a37b09c..e97bab3c 100644 --- a/src/PostgrestClient.ts +++ b/src/PostgrestClient.ts @@ -2,7 +2,7 @@ import PostgrestQueryBuilder from './PostgrestQueryBuilder' import PostgrestFilterBuilder from './PostgrestFilterBuilder' import PostgrestBuilder from './PostgrestBuilder' import { DEFAULT_HEADERS } from './constants' -import { Fetch, GenericSchema } from './types' +import { Fetch, GenericSchema, ClientServerOptions, GetGenericDatabaseWithOptions } from './types' /** * PostgREST client. @@ -16,11 +16,16 @@ import { Fetch, GenericSchema } from './types' */ export default class PostgrestClient< Database = any, - SchemaName extends string & keyof Database = 'public' extends keyof Database + ClientOptions extends ClientServerOptions = GetGenericDatabaseWithOptions< + Database, + { postgrestVersion: '12' } + >['options'], + SchemaName extends string & + keyof GetGenericDatabaseWithOptions['db'] = 'public' extends keyof GetGenericDatabaseWithOptions['db'] ? 'public' - : string & keyof Database, - Schema extends GenericSchema = Database[SchemaName] extends GenericSchema - ? Database[SchemaName] + : string & keyof GetGenericDatabaseWithOptions['db'], + Schema extends GenericSchema = GetGenericDatabaseWithOptions['db'][SchemaName] extends GenericSchema + ? GetGenericDatabaseWithOptions['db'][SchemaName] : any > { url: string @@ -59,16 +64,16 @@ export default class PostgrestClient< from< TableName extends string & keyof Schema['Tables'], Table extends Schema['Tables'][TableName] - >(relation: TableName): PostgrestQueryBuilder + >(relation: TableName): PostgrestQueryBuilder from( relation: ViewName - ): PostgrestQueryBuilder + ): PostgrestQueryBuilder /** * Perform a query on a table or a view. * * @param relation - The table or view name to query */ - from(relation: string): PostgrestQueryBuilder { + from(relation: string): PostgrestQueryBuilder { const url = new URL(`${this.url}/${relation}`) return new PostgrestQueryBuilder(url, { headers: { ...this.headers }, @@ -84,10 +89,11 @@ export default class PostgrestClient< * * @param schema - The schema to query */ - schema( + schema['db']>( schema: DynamicSchema ): PostgrestClient< Database, + ClientOptions, DynamicSchema, Database[DynamicSchema] extends GenericSchema ? Database[DynamicSchema] : any > { @@ -134,6 +140,7 @@ export default class PostgrestClient< count?: 'exact' | 'planned' | 'estimated' } = {} ): PostgrestFilterBuilder< + ClientOptions, Schema, Fn['Returns'] extends any[] ? Fn['Returns'][number] extends Record @@ -176,6 +183,6 @@ export default class PostgrestClient< body, fetch: this.fetch, allowEmpty: false, - } as unknown as PostgrestBuilder) + } as unknown as PostgrestBuilder) } } diff --git a/src/PostgrestFilterBuilder.ts b/src/PostgrestFilterBuilder.ts index c0de7d33..78b053ef 100644 --- a/src/PostgrestFilterBuilder.ts +++ b/src/PostgrestFilterBuilder.ts @@ -1,6 +1,6 @@ import PostgrestTransformBuilder from './PostgrestTransformBuilder' import { JsonPathToAccessor, JsonPathToType } from './select-query-parser/utils' -import { GenericSchema } from './types' +import { ClientServerOptions, GenericSchema } from './types' type FilterOperator = | 'eq' @@ -69,13 +69,23 @@ type ResolveFilterRelationshipValue< : unknown : never +export type InvalidMethodError = { Error: S } + export default class PostgrestFilterBuilder< + ClientOptions extends ClientServerOptions, Schema extends GenericSchema, Row extends Record, Result, RelationName = unknown, Relationships = unknown -> extends PostgrestTransformBuilder { +> extends PostgrestTransformBuilder< + ClientOptions, + Schema, + Row, + Result, + RelationName, + Relationships +> { /** * Match only rows where `column` is equal to `value`. * diff --git a/src/PostgrestQueryBuilder.ts b/src/PostgrestQueryBuilder.ts index 44e7320a..23059e9a 100644 --- a/src/PostgrestQueryBuilder.ts +++ b/src/PostgrestQueryBuilder.ts @@ -1,9 +1,10 @@ import PostgrestBuilder from './PostgrestBuilder' import PostgrestFilterBuilder from './PostgrestFilterBuilder' import { GetResult } from './select-query-parser/result' -import { Fetch, GenericSchema, GenericTable, GenericView } from './types' +import { ClientServerOptions, Fetch, GenericSchema, GenericTable, GenericView } from './types' export default class PostgrestQueryBuilder< + ClientOptions extends ClientServerOptions, Schema extends GenericSchema, Relation extends GenericTable | GenericView, RelationName = unknown, @@ -56,7 +57,14 @@ export default class PostgrestQueryBuilder< */ select< Query extends string = '*', - ResultOne = GetResult + ResultOne = GetResult< + Schema, + Relation['Row'], + RelationName, + Relationships, + Query, + ClientOptions + > >( columns?: Query, { @@ -66,7 +74,14 @@ export default class PostgrestQueryBuilder< head?: boolean count?: 'exact' | 'planned' | 'estimated' } = {} - ): PostgrestFilterBuilder { + ): PostgrestFilterBuilder< + ClientOptions, + Schema, + Relation['Row'], + ResultOne[], + RelationName, + Relationships + > { const method = head ? 'HEAD' : 'GET' // Remove whitespaces except when quoted let quoted = false @@ -94,7 +109,7 @@ export default class PostgrestQueryBuilder< schema: this.schema, fetch: this.fetch, allowEmpty: false, - } as unknown as PostgrestBuilder) + } as unknown as PostgrestBuilder) } // TODO(v3): Make `defaultToNull` consistent for both single & bulk inserts. @@ -103,14 +118,28 @@ export default class PostgrestQueryBuilder< options?: { count?: 'exact' | 'planned' | 'estimated' } - ): PostgrestFilterBuilder + ): PostgrestFilterBuilder< + ClientOptions, + Schema, + Relation['Row'], + null, + RelationName, + Relationships + > insert( values: Row[], options?: { count?: 'exact' | 'planned' | 'estimated' defaultToNull?: boolean } - ): PostgrestFilterBuilder + ): PostgrestFilterBuilder< + ClientOptions, + Schema, + Relation['Row'], + null, + RelationName, + Relationships + > /** * Perform an INSERT into the table or view. * @@ -146,7 +175,14 @@ export default class PostgrestQueryBuilder< count?: 'exact' | 'planned' | 'estimated' defaultToNull?: boolean } = {} - ): PostgrestFilterBuilder { + ): PostgrestFilterBuilder< + ClientOptions, + Schema, + Relation['Row'], + null, + RelationName, + Relationships + > { const method = 'POST' const prefersHeaders = [] @@ -177,7 +213,7 @@ export default class PostgrestQueryBuilder< body: values, fetch: this.fetch, allowEmpty: false, - } as unknown as PostgrestBuilder) + } as unknown as PostgrestBuilder) } // TODO(v3): Make `defaultToNull` consistent for both single & bulk upserts. @@ -188,7 +224,14 @@ export default class PostgrestQueryBuilder< ignoreDuplicates?: boolean count?: 'exact' | 'planned' | 'estimated' } - ): PostgrestFilterBuilder + ): PostgrestFilterBuilder< + ClientOptions, + Schema, + Relation['Row'], + null, + RelationName, + Relationships + > upsert( values: Row[], options?: { @@ -197,7 +240,14 @@ export default class PostgrestQueryBuilder< count?: 'exact' | 'planned' | 'estimated' defaultToNull?: boolean } - ): PostgrestFilterBuilder + ): PostgrestFilterBuilder< + ClientOptions, + Schema, + Relation['Row'], + null, + RelationName, + Relationships + > /** * Perform an UPSERT on the table or view. Depending on the column(s) passed * to `onConflict`, `.upsert()` allows you to perform the equivalent of @@ -249,7 +299,14 @@ export default class PostgrestQueryBuilder< count?: 'exact' | 'planned' | 'estimated' defaultToNull?: boolean } = {} - ): PostgrestFilterBuilder { + ): PostgrestFilterBuilder< + ClientOptions, + Schema, + Relation['Row'], + null, + RelationName, + Relationships + > { const method = 'POST' const prefersHeaders = [`resolution=${ignoreDuplicates ? 'ignore' : 'merge'}-duplicates`] @@ -282,7 +339,7 @@ export default class PostgrestQueryBuilder< body: values, fetch: this.fetch, allowEmpty: false, - } as unknown as PostgrestBuilder) + } as unknown as PostgrestBuilder) } /** @@ -313,7 +370,14 @@ export default class PostgrestQueryBuilder< }: { count?: 'exact' | 'planned' | 'estimated' } = {} - ): PostgrestFilterBuilder { + ): PostgrestFilterBuilder< + ClientOptions, + Schema, + Relation['Row'], + null, + RelationName, + Relationships + > { const method = 'PATCH' const prefersHeaders = [] if (this.headers['Prefer']) { @@ -332,7 +396,7 @@ export default class PostgrestQueryBuilder< body: values, fetch: this.fetch, allowEmpty: false, - } as unknown as PostgrestBuilder) + } as unknown as PostgrestBuilder) } /** @@ -358,7 +422,14 @@ export default class PostgrestQueryBuilder< count, }: { count?: 'exact' | 'planned' | 'estimated' - } = {}): PostgrestFilterBuilder { + } = {}): PostgrestFilterBuilder< + ClientOptions, + Schema, + Relation['Row'], + null, + RelationName, + Relationships + > { const method = 'DELETE' const prefersHeaders = [] if (count) { @@ -376,6 +447,6 @@ export default class PostgrestQueryBuilder< schema: this.schema, fetch: this.fetch, allowEmpty: false, - } as unknown as PostgrestBuilder) + } as unknown as PostgrestBuilder) } } diff --git a/src/PostgrestTransformBuilder.ts b/src/PostgrestTransformBuilder.ts index c9fb5781..d8950ec8 100644 --- a/src/PostgrestTransformBuilder.ts +++ b/src/PostgrestTransformBuilder.ts @@ -1,14 +1,16 @@ import PostgrestBuilder from './PostgrestBuilder' import { GetResult } from './select-query-parser/result' -import { GenericSchema, CheckMatchingArrayTypes } from './types' +import { GenericSchema, CheckMatchingArrayTypes, ClientServerOptions } from './types' export default class PostgrestTransformBuilder< + ClientOptions extends ClientServerOptions, Schema extends GenericSchema, Row extends Record, Result, RelationName = unknown, - Relationships = unknown -> extends PostgrestBuilder { + Relationships = unknown, + Method = unknown +> extends PostgrestBuilder { /** * Perform a SELECT on the query result. * @@ -20,10 +22,18 @@ export default class PostgrestTransformBuilder< */ select< Query extends string = '*', - NewResultOne = GetResult + NewResultOne = GetResult >( columns?: Query - ): PostgrestTransformBuilder { + ): PostgrestTransformBuilder< + ClientOptions, + Schema, + Row, + NewResultOne[], + RelationName, + Relationships, + Method + > { // Remove whitespaces except when quoted let quoted = false const cleanedColumns = (columns ?? '*') @@ -44,11 +54,13 @@ export default class PostgrestTransformBuilder< } this.headers['Prefer'] += 'return=representation' return this as unknown as PostgrestTransformBuilder< + ClientOptions, Schema, Row, NewResultOne[], RelationName, - Relationships + Relationships, + Method > } @@ -188,11 +200,12 @@ export default class PostgrestTransformBuilder< * Query result must be one row (e.g. using `.limit(1)`), otherwise this * returns an error. */ - single< - ResultOne = Result extends (infer ResultOne)[] ? ResultOne : never - >(): PostgrestBuilder { + single(): PostgrestBuilder< + ClientOptions, + ResultOne + > { this.headers['Accept'] = 'application/vnd.pgrst.object+json' - return this as unknown as PostgrestBuilder + return this as unknown as PostgrestBuilder } /** @@ -203,7 +216,7 @@ export default class PostgrestTransformBuilder< */ maybeSingle< ResultOne = Result extends (infer ResultOne)[] ? ResultOne : never - >(): PostgrestBuilder { + >(): PostgrestBuilder { // Temporary partial fix for https://github.com/supabase/postgrest-js/issues/361 // Issue persists e.g. for `.insert([...]).select().maybeSingle()` if (this.method === 'GET') { @@ -212,23 +225,23 @@ export default class PostgrestTransformBuilder< this.headers['Accept'] = 'application/vnd.pgrst.object+json' } this.isMaybeSingle = true - return this as unknown as PostgrestBuilder + return this as unknown as PostgrestBuilder } /** * Return `data` as a string in CSV format. */ - csv(): PostgrestBuilder { + csv(): PostgrestBuilder { this.headers['Accept'] = 'text/csv' - return this as unknown as PostgrestBuilder + return this as unknown as PostgrestBuilder } /** * Return `data` as an object in [GeoJSON](https://geojson.org) format. */ - geojson(): PostgrestBuilder> { + geojson(): PostgrestBuilder> { this.headers['Accept'] = 'application/geo+json' - return this as unknown as PostgrestBuilder> + return this as unknown as PostgrestBuilder> } /** @@ -270,7 +283,9 @@ export default class PostgrestTransformBuilder< buffers?: boolean wal?: boolean format?: 'json' | 'text' - } = {}): PostgrestBuilder[]> | PostgrestBuilder { + } = {}): + | PostgrestBuilder[]> + | PostgrestBuilder { const options = [ analyze ? 'analyze' : null, verbose ? 'verbose' : null, @@ -285,8 +300,9 @@ export default class PostgrestTransformBuilder< this.headers[ 'Accept' ] = `application/vnd.pgrst.plan+${format}; for="${forMediatype}"; options=${options};` - if (format === 'json') return this as unknown as PostgrestBuilder[]> - else return this as unknown as PostgrestBuilder + if (format === 'json') + return this as unknown as PostgrestBuilder[]> + else return this as unknown as PostgrestBuilder } /** @@ -310,18 +326,22 @@ export default class PostgrestTransformBuilder< * @deprecated Use overrideTypes() method at the end of your call chain instead */ returns(): PostgrestTransformBuilder< + ClientOptions, Schema, Row, CheckMatchingArrayTypes, RelationName, - Relationships + Relationships, + Method > { return this as unknown as PostgrestTransformBuilder< + ClientOptions, Schema, Row, CheckMatchingArrayTypes, RelationName, - Relationships + Relationships, + Method > } } diff --git a/src/index.ts b/src/index.ts index 466a71bf..23b12d52 100644 --- a/src/index.ts +++ b/src/index.ts @@ -28,6 +28,7 @@ export type { PostgrestResponseSuccess, PostgrestSingleResponse, PostgrestMaybeSingleResponse, + ClientServerOptions, } from './types' // https://github.com/supabase/postgrest-js/issues/551 // To be replaced with a helper type that only uses public types diff --git a/src/select-query-parser/result.ts b/src/select-query-parser/result.ts index a32567c2..e0eb5bde 100644 --- a/src/select-query-parser/result.ts +++ b/src/select-query-parser/result.ts @@ -1,4 +1,4 @@ -import { GenericTable } from '../types' +import { ClientServerOptions, GenericTable } from '../types' import { ContainsNull, GenericRelationship, PostgreSQLTypes } from './types' import { Ast, ParseQuery } from './parser' import { @@ -21,6 +21,9 @@ import { SelectQueryError, } from './utils' +export type SpreadOnManyEnabled = + postgrestVersion extends `13${string}` ? true : false + /** * Main entry point for constructing the result type of a PostgREST query. * @@ -35,7 +38,8 @@ export type GetResult< Row extends Record, RelationName, Relationships, - Query extends string + Query extends string, + ClientOptions extends ClientServerOptions > = IsAny extends true ? ParseQuery extends infer ParsedQuery ? ParsedQuery extends Ast.Node[] @@ -54,7 +58,7 @@ export type GetResult< ? ParsedQuery extends Ast.Node[] ? RelationName extends string ? Relationships extends GenericRelationship[] - ? ProcessNodes + ? ProcessNodes : SelectQueryError<'Invalid Relationships cannot infer result type'> : SelectQueryError<'Invalid RelationName cannot infer result type'> : ParsedQuery @@ -173,6 +177,7 @@ export type RPCCallNodes< * @param Acc - Accumulator for the constructed type. */ export type ProcessNodes< + ClientOptions extends ClientServerOptions, Schema extends GenericSchema, Row extends Record, RelationName extends string, @@ -183,9 +188,24 @@ export type ProcessNodes< ? Nodes extends [infer FirstNode, ...infer RestNodes] ? FirstNode extends Ast.Node ? RestNodes extends Ast.Node[] - ? ProcessNode extends infer FieldResult + ? ProcessNode< + ClientOptions, + Schema, + Row, + RelationName, + Relationships, + FirstNode + > extends infer FieldResult ? FieldResult extends Record - ? ProcessNodes + ? ProcessNodes< + ClientOptions, + Schema, + Row, + RelationName, + Relationships, + RestNodes, + Acc & FieldResult + > : FieldResult extends SelectQueryError ? SelectQueryError : SelectQueryError<'Could not retrieve a valid record or error value'> @@ -205,6 +225,7 @@ export type ProcessNodes< * @param NodeType - The Node to process. */ export type ProcessNode< + ClientOptions extends ClientServerOptions, Schema extends GenericSchema, Row extends Record, RelationName extends string, @@ -215,9 +236,23 @@ export type ProcessNode< NodeType['type'] extends Ast.StarNode['type'] // If the selection is * ? Row : NodeType['type'] extends Ast.SpreadNode['type'] // If the selection is a ...spread - ? ProcessSpreadNode> + ? ProcessSpreadNode< + ClientOptions, + Schema, + Row, + RelationName, + Relationships, + Extract + > : NodeType['type'] extends Ast.FieldNode['type'] - ? ProcessFieldNode> + ? ProcessFieldNode< + ClientOptions, + Schema, + Row, + RelationName, + Relationships, + Extract + > : SelectQueryError<'Unsupported node type.'> /** @@ -230,6 +265,7 @@ export type ProcessNode< * @param Field - The FieldNode to process. */ type ProcessFieldNode< + ClientOptions extends ClientServerOptions, Schema extends GenericSchema, Row extends Record, RelationName extends string, @@ -238,7 +274,7 @@ type ProcessFieldNode< > = Field['children'] extends [] ? {} : IsNonEmptyArray extends true // Has embedded resource? - ? ProcessEmbeddedResource + ? ProcessEmbeddedResource : ProcessSimpleField type ResolveJsonPathType< @@ -303,6 +339,7 @@ type ProcessSimpleField< * @param Field - The FieldNode to process. */ export type ProcessEmbeddedResource< + ClientOptions extends ClientServerOptions, Schema extends GenericSchema, Relationships extends GenericRelationship[], Field extends Ast.FieldNode, @@ -313,7 +350,7 @@ export type ProcessEmbeddedResource< relation: GenericRelationship & { match: 'refrel' | 'col' | 'fkname' } direction: string } - ? ProcessEmbeddedResourceResult + ? ProcessEmbeddedResourceResult : // Otherwise the Resolved is a SelectQueryError return it { [K in GetFieldNodeResultName]: Resolved } : { @@ -325,6 +362,7 @@ export type ProcessEmbeddedResource< * Helper type to process the result of an embedded resource. */ type ProcessEmbeddedResourceResult< + ClientOptions extends ClientServerOptions, Schema extends GenericSchema, Resolved extends { referencedTable: Pick @@ -334,6 +372,7 @@ type ProcessEmbeddedResourceResult< Field extends Ast.FieldNode, CurrentTableOrView extends keyof TablesAndViews > = ProcessNodes< + ClientOptions, Schema, Resolved['referencedTable']['Row'], Field['name'], @@ -392,21 +431,46 @@ type ProcessEmbeddedResourceResult< * @param Spread - The SpreadNode to process. */ type ProcessSpreadNode< + ClientOptions extends ClientServerOptions, Schema extends GenericSchema, Row extends Record, RelationName extends string, Relationships extends GenericRelationship[], Spread extends Ast.SpreadNode -> = ProcessNode extends infer Result +> = ProcessNode< + ClientOptions, + Schema, + Row, + RelationName, + Relationships, + Spread['target'] +> extends infer Result ? Result extends SelectQueryError ? SelectQueryError : ExtractFirstProperty extends unknown[] - ? { - [K in Spread['target']['name']]: SelectQueryError<`"${RelationName}" and "${Spread['target']['name']}" do not form a many-to-one or one-to-one relationship spread not possible`> - } + ? SpreadOnManyEnabled extends true // Spread over an many-to-many relationship, turn all the result fields into correlated arrays + ? ProcessManyToManySpreadNodeResult + : { + [K in Spread['target']['name']]: SelectQueryError<`"${RelationName}" and "${Spread['target']['name']}" do not form a many-to-one or one-to-one relationship spread not possible`> + } : ProcessSpreadNodeResult : never +/** + * Helper type to process the result of a many-to-many spread node. + * Converts all fields in the spread object into arrays. + */ +type ProcessManyToManySpreadNodeResult = Result extends Record< + string, + SelectQueryError | null +> + ? Result + : ExtractFirstProperty extends infer SpreadedObject + ? SpreadedObject extends Array> + ? { [K in keyof SpreadedObject[number]]: Array } + : SelectQueryError<'An error occurred spreading the many-to-many object'> + : SelectQueryError<'An error occurred spreading the many-to-many object'> + /** * Helper type to process the result of a spread node. */ diff --git a/src/types.ts b/src/types.ts index 51d58a70..2cddf70f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,6 +1,6 @@ import PostgrestError from './PostgrestError' import { ContainsNull } from './select-query-parser/types' -import { SelectQueryError } from './select-query-parser/utils' +import { IsAny, SelectQueryError } from './select-query-parser/utils' export type Fetch = typeof fetch @@ -71,8 +71,34 @@ export type GenericSchema = { Functions: Record } +export type ClientServerOptions = { + postgrestVersion?: string +} + +export type DatabaseWithOptions = { + db: Database + options: Options +} + +const INTERNAL_SUPABASE_OPTIONS = '__internal_supabase' + +export type GetGenericDatabaseWithOptions< + Database, + Opts extends ClientServerOptions = { postgrestVersion: '12' } +> = IsAny extends true + ? DatabaseWithOptions + : typeof INTERNAL_SUPABASE_OPTIONS extends keyof Database + ? Database[typeof INTERNAL_SUPABASE_OPTIONS] extends ClientServerOptions + ? DatabaseWithOptions< + Omit, + Database[typeof INTERNAL_SUPABASE_OPTIONS] + > + : DatabaseWithOptions, Opts> + : DatabaseWithOptions + // https://twitter.com/mattpocockuk/status/1622730173446557697 export type Prettify = { [K in keyof T]: T[K] } & {} + // https://github.com/sindresorhus/type-fest export type SimplifyDeep = ConditionalSimplifyDeep< Type, diff --git a/test/basic.ts b/test/basic.ts index d2317cab..31b9b9e4 100644 --- a/test/basic.ts +++ b/test/basic.ts @@ -319,7 +319,12 @@ describe('custom prefer headers with ', () => { }) test('switch schema', async () => { - const postgrest = new PostgrestClient(REST_URL, { schema: 'personal' }) + const postgrest = new PostgrestClient( + REST_URL, + { + schema: 'personal', + } + ) const res = await postgrest.from('users').select() expect(res).toMatchInlineSnapshot(` Object { diff --git a/test/db/docker-compose.yml b/test/db/docker-compose.yml index c2f91a1d..59df0632 100644 --- a/test/db/docker-compose.yml +++ b/test/db/docker-compose.yml @@ -2,7 +2,21 @@ version: '3' services: - rest: + rest13: + image: postgrest/postgrest:v13.0.0 + ports: + - '3001:3000' + environment: + PGRST_DB_URI: postgres://postgres:postgres@db:5432/postgres + PGRST_DB_SCHEMAS: public,personal + PGRST_DB_EXTRA_SEARCH_PATH: extensions + PGRST_DB_ANON_ROLE: postgres + PGRST_DB_PLAN_ENABLED: 1 + PGRST_DB_TX_END: commit-allow-override + PGRST_DB_AGGREGATES_ENABLED: true + depends_on: + - db + rest12: image: postgrest/postgrest:v12.2.0 ports: - '3000:3000' diff --git a/test/index.test-d.ts b/test/index.test-d.ts index b357050c..0e1185d6 100644 --- a/test/index.test-d.ts +++ b/test/index.test-d.ts @@ -4,9 +4,11 @@ import { PostgrestClient, PostgrestError } from '../src/index' import { Prettify } from '../src/types' import { Json } from '../src/select-query-parser/types' import { Database } from './types.override' +import { Database as DatabaseWithOptions } from './types.override-with-options-postgrest13' const REST_URL = 'http://localhost:3000' const postgrest = new PostgrestClient(REST_URL) +const postgrestWithOptions = new PostgrestClient(REST_URL) // table invalid type { @@ -295,20 +297,9 @@ const postgrest = new PostgrestClient(REST_URL) }[] >(result.data) } -// Json string Accessor with custom types overrides +// Check that client options __internal_supabase isn't considered like the other schemas { - const result = await postgrest - .schema('personal') - .from('users') - .select('data->bar->>baz, data->>en, data->>bar') - if (result.error) { - throw new Error(result.error.message) - } - expectType< - { - baz: string - en: 'ONE' | 'TWO' | 'THREE' - bar: string - }[] - >(result.data) + await postgrestWithOptions + // @ts-expect-error supabase internal shouldn't be available as one of the selectable schema + .schema('__internal_supabase') } diff --git a/test/relationships.ts b/test/relationships.ts index c826f165..2acdae54 100644 --- a/test/relationships.ts +++ b/test/relationships.ts @@ -1,8 +1,12 @@ import { PostgrestClient } from '../src/index' import { Database } from './types.override' +import { Database as DatabaseWithOptions13 } from './types.override-with-options-postgrest13' const REST_URL = 'http://localhost:3000' export const postgrest = new PostgrestClient(REST_URL) +const REST_URL_13 = 'http://localhost:3001' +const postgrest13 = new PostgrestClient(REST_URL_13) +const postgrest13FromDatabaseTypes = new PostgrestClient(REST_URL_13) const userColumn: 'catchphrase' | 'username' = 'username' @@ -146,7 +150,7 @@ export const selectParams = { }, selectSpreadOnManyRelation: { from: 'channels', - select: 'id, ...messages(id, message)', + select: 'channel_id:id, ...messages(id, message)', }, selectWithDuplicatesFields: { from: 'channels', @@ -345,6 +349,12 @@ export const selectQueries = { selectSpreadOnManyRelation: postgrest .from(selectParams.selectSpreadOnManyRelation.from) .select(selectParams.selectSpreadOnManyRelation.select), + selectSpreadOnManyRelation13: postgrest13 + .from(selectParams.selectSpreadOnManyRelation.from) + .select(selectParams.selectSpreadOnManyRelation.select), + selectSpreadOnManyRelation13FromDatabaseType: postgrest13FromDatabaseTypes + .from(selectParams.selectSpreadOnManyRelation.from) + .select(selectParams.selectSpreadOnManyRelation.select), selectWithDuplicatesFields: postgrest .from(selectParams.selectWithDuplicatesFields.from) .select(selectParams.selectWithDuplicatesFields.select), @@ -1734,7 +1744,7 @@ test('join with same dest twice column hinting', async () => { `) }) -test('join with same dest twice column hinting', async () => { +test('select spread on many relation', async () => { const res = await selectQueries.selectSpreadOnManyRelation.limit(1).single() expect(res).toMatchInlineSnapshot(` Object { @@ -1752,6 +1762,48 @@ test('join with same dest twice column hinting', async () => { `) }) +test('select spread on many relation postgrest13', async () => { + const res = await selectQueries.selectSpreadOnManyRelation13.limit(1).single() + expect(res).toMatchInlineSnapshot(` + Object { + "count": null, + "data": Object { + "channel_id": 1, + "id": Array [ + 1, + ], + "message": Array [ + "Hello World 👋", + ], + }, + "error": null, + "status": 200, + "statusText": "OK", + } + `) +}) + +test('select spread on many relation postgrest13FromDatabaseTypes', async () => { + const res = await selectQueries.selectSpreadOnManyRelation13FromDatabaseType.limit(1).single() + expect(res).toMatchInlineSnapshot(` + Object { + "count": null, + "data": Object { + "channel_id": 1, + "id": Array [ + 1, + ], + "message": Array [ + "Hello World 👋", + ], + }, + "error": null, + "status": 200, + "statusText": "OK", + } + `) +}) + test('multiple times the same column in selection', async () => { const res = await selectQueries.selectWithDuplicatesFields.limit(1).single() expect(res).toMatchInlineSnapshot(` diff --git a/test/returns.test-d.ts b/test/returns.test-d.ts index f6e5f5a8..f9760284 100644 --- a/test/returns.test-d.ts +++ b/test/returns.test-d.ts @@ -53,6 +53,7 @@ const postgrest = new PostgrestClient(REST_URL) .returns<{ username: string }[]>() expectType< PostgrestBuilder< + { postgrestVersion: '12' }, { Error: 'Type mismatch: Cannot cast single object to array type. Remove Array wrapper from return type or make sure you are not using .single() up in the calling chain' }, diff --git a/test/select-query-parser/result.test-d.ts b/test/select-query-parser/result.test-d.ts index b2564258..e8ec925d 100644 --- a/test/select-query-parser/result.test-d.ts +++ b/test/select-query-parser/result.test-d.ts @@ -15,7 +15,8 @@ type SelectQueryFromTableResult< Database['public']['Tables'][TableName]['Row'], TableName, Database['public']['Tables'][TableName]['Relationships'], - Q + Q, + { postgrestVersion: '12' } > // This test file is here to help develop, debug and maintain the GetResult @@ -130,7 +131,8 @@ type SelectQueryFromTableResult< Database['personal']['Tables'][TableName]['Row'], TableName, Database['personal']['Tables'][TableName]['Relationships'], - Q + Q, + { postgrestVersion: '12' } > // Should work with Json object accessor { diff --git a/test/select-query-parser/select.test-d.ts b/test/select-query-parser/select.test-d.ts index 11726d9f..3d83e7ac 100644 --- a/test/select-query-parser/select.test-d.ts +++ b/test/select-query-parser/select.test-d.ts @@ -649,17 +649,43 @@ type Schema = Database['public'] expectType>(true) } -// join with same dest twice column hinting +// spread over a many relation with postgrest12 { const { data } = await selectQueries.selectSpreadOnManyRelation.limit(1).single() let result: Exclude let expected: { - id: number + channel_id: number messages: SelectQueryError<'"channels" and "messages" do not form a many-to-one or one-to-one relationship spread not possible'> } expectType>(true) } +// spread over a many relation with postgrest13 +{ + const { data } = await selectQueries.selectSpreadOnManyRelation13.limit(1).single() + let result: Exclude + let expected: { + channel_id: number + id: Array + message: Array + } + expectType>(true) +} + +// spread over a many relation with postgrest13 passed within the Database type +{ + const { data } = await selectQueries.selectSpreadOnManyRelation13FromDatabaseType + .limit(1) + .single() + let result: Exclude + let expected: { + channel_id: number + id: Array + message: Array + } + expectType>(true) +} + // multiple times the same column in selection { const { data } = await selectQueries.selectWithDuplicatesFields.limit(1).single() diff --git a/test/types.generated-with-options-postgrest13.ts b/test/types.generated-with-options-postgrest13.ts new file mode 100644 index 00000000..7cad255d --- /dev/null +++ b/test/types.generated-with-options-postgrest13.ts @@ -0,0 +1,662 @@ +export type Json = unknown + +export type Database = { + // This is a dummy non existent schema to allow automatically passing down options + // to the instanciated client at type levels from the introspected database + __internal_supabase: { + postgrestVersion: '13.0.12' + // We make this still abide to `GenericSchema` to allow types helpers bellow to work the same + Tables: { + [_ in never]: never + } + Views: { + [_ in never]: never + } + Functions: { + [_ in never]: never + } + Enums: { + [_ in never]: never + } + CompositeTypes: { + [_ in never]: never + } + } + personal: { + Tables: { + users: { + Row: { + age_range: unknown | null + data: Json | null + status: Database['public']['Enums']['user_status'] | null + username: string + } + Insert: { + age_range?: unknown | null + data?: Json | null + status?: Database['public']['Enums']['user_status'] | null + username: string + } + Update: { + age_range?: unknown | null + data?: Json | null + status?: Database['public']['Enums']['user_status'] | null + username?: string + } + Relationships: [] + } + } + Views: { + [_ in never]: never + } + Functions: { + get_status: { + Args: { + name_param: string + } + Returns: Database['public']['Enums']['user_status'] + } + } + Enums: { + user_status: 'ONLINE' | 'OFFLINE' + } + CompositeTypes: { + [_ in never]: never + } + } + public: { + Tables: { + best_friends: { + Row: { + first_user: string + id: number + second_user: string + third_wheel: string | null + } + Insert: { + first_user: string + id?: number + second_user: string + third_wheel?: string | null + } + Update: { + first_user?: string + id?: number + second_user?: string + third_wheel?: string | null + } + Relationships: [ + { + foreignKeyName: 'best_friends_first_user_fkey' + columns: ['first_user'] + isOneToOne: false + referencedRelation: 'non_updatable_view' + referencedColumns: ['username'] + }, + { + foreignKeyName: 'best_friends_first_user_fkey' + columns: ['first_user'] + isOneToOne: false + referencedRelation: 'updatable_view' + referencedColumns: ['username'] + }, + { + foreignKeyName: 'best_friends_first_user_fkey' + columns: ['first_user'] + isOneToOne: false + referencedRelation: 'users' + referencedColumns: ['username'] + }, + { + foreignKeyName: 'best_friends_second_user_fkey' + columns: ['second_user'] + isOneToOne: false + referencedRelation: 'non_updatable_view' + referencedColumns: ['username'] + }, + { + foreignKeyName: 'best_friends_second_user_fkey' + columns: ['second_user'] + isOneToOne: false + referencedRelation: 'updatable_view' + referencedColumns: ['username'] + }, + { + foreignKeyName: 'best_friends_second_user_fkey' + columns: ['second_user'] + isOneToOne: false + referencedRelation: 'users' + referencedColumns: ['username'] + }, + { + foreignKeyName: 'best_friends_third_wheel_fkey' + columns: ['third_wheel'] + isOneToOne: false + referencedRelation: 'non_updatable_view' + referencedColumns: ['username'] + }, + { + foreignKeyName: 'best_friends_third_wheel_fkey' + columns: ['third_wheel'] + isOneToOne: false + referencedRelation: 'updatable_view' + referencedColumns: ['username'] + }, + { + foreignKeyName: 'best_friends_third_wheel_fkey' + columns: ['third_wheel'] + isOneToOne: false + referencedRelation: 'users' + referencedColumns: ['username'] + } + ] + } + booking: { + Row: { + hotel_id: number | null + id: number + } + Insert: { + hotel_id?: number | null + id?: number + } + Update: { + hotel_id?: number | null + id?: number + } + Relationships: [ + { + foreignKeyName: 'booking_hotel_id_fkey' + columns: ['hotel_id'] + isOneToOne: false + referencedRelation: 'hotel' + referencedColumns: ['id'] + } + ] + } + categories: { + Row: { + description: string | null + id: number + name: string + } + Insert: { + description?: string | null + id?: number + name: string + } + Update: { + description?: string | null + id?: number + name?: string + } + Relationships: [] + } + channel_details: { + Row: { + details: string | null + id: number + } + Insert: { + details?: string | null + id: number + } + Update: { + details?: string | null + id?: number + } + Relationships: [ + { + foreignKeyName: 'channel_details_id_fkey' + columns: ['id'] + isOneToOne: true + referencedRelation: 'channels' + referencedColumns: ['id'] + } + ] + } + channels: { + Row: { + data: Json | null + id: number + slug: string | null + } + Insert: { + data?: Json | null + id?: number + slug?: string | null + } + Update: { + data?: Json | null + id?: number + slug?: string | null + } + Relationships: [] + } + collections: { + Row: { + description: string | null + id: number + parent_id: number | null + } + Insert: { + description?: string | null + id?: number + parent_id?: number | null + } + Update: { + description?: string | null + id?: number + parent_id?: number | null + } + Relationships: [ + { + foreignKeyName: 'collections_parent_id_fkey' + columns: ['parent_id'] + isOneToOne: false + referencedRelation: 'collections' + referencedColumns: ['id'] + } + ] + } + cornercase: { + Row: { + array_column: string[] | null + 'column whitespace': string | null + id: number + } + Insert: { + array_column?: string[] | null + 'column whitespace'?: string | null + id: number + } + Update: { + array_column?: string[] | null + 'column whitespace'?: string | null + id?: number + } + Relationships: [] + } + hotel: { + Row: { + id: number + name: string | null + } + Insert: { + id?: number + name?: string | null + } + Update: { + id?: number + name?: string | null + } + Relationships: [] + } + messages: { + Row: { + channel_id: number + data: Json | null + id: number + message: string | null + username: string + } + Insert: { + channel_id: number + data?: Json | null + id?: number + message?: string | null + username: string + } + Update: { + channel_id?: number + data?: Json | null + id?: number + message?: string | null + username?: string + } + Relationships: [ + { + foreignKeyName: 'messages_channel_id_fkey' + columns: ['channel_id'] + isOneToOne: false + referencedRelation: 'channels' + referencedColumns: ['id'] + }, + { + foreignKeyName: 'messages_username_fkey' + columns: ['username'] + isOneToOne: false + referencedRelation: 'non_updatable_view' + referencedColumns: ['username'] + }, + { + foreignKeyName: 'messages_username_fkey' + columns: ['username'] + isOneToOne: false + referencedRelation: 'updatable_view' + referencedColumns: ['username'] + }, + { + foreignKeyName: 'messages_username_fkey' + columns: ['username'] + isOneToOne: false + referencedRelation: 'users' + referencedColumns: ['username'] + } + ] + } + product_categories: { + Row: { + category_id: number + product_id: number + } + Insert: { + category_id: number + product_id: number + } + Update: { + category_id?: number + product_id?: number + } + Relationships: [ + { + foreignKeyName: 'product_categories_category_id_fkey' + columns: ['category_id'] + isOneToOne: false + referencedRelation: 'categories' + referencedColumns: ['id'] + }, + { + foreignKeyName: 'product_categories_product_id_fkey' + columns: ['product_id'] + isOneToOne: false + referencedRelation: 'products' + referencedColumns: ['id'] + } + ] + } + products: { + Row: { + description: string | null + id: number + name: string + price: number + } + Insert: { + description?: string | null + id?: number + name: string + price: number + } + Update: { + description?: string | null + id?: number + name?: string + price?: number + } + Relationships: [] + } + shops: { + Row: { + address: string | null + id: number + shop_geom: unknown | null + } + Insert: { + address?: string | null + id: number + shop_geom?: unknown | null + } + Update: { + address?: string | null + id?: number + shop_geom?: unknown | null + } + Relationships: [] + } + user_profiles: { + Row: { + id: number + username: string | null + } + Insert: { + id?: number + username?: string | null + } + Update: { + id?: number + username?: string | null + } + Relationships: [ + { + foreignKeyName: 'user_profiles_username_fkey' + columns: ['username'] + isOneToOne: false + referencedRelation: 'non_updatable_view' + referencedColumns: ['username'] + }, + { + foreignKeyName: 'user_profiles_username_fkey' + columns: ['username'] + isOneToOne: false + referencedRelation: 'updatable_view' + referencedColumns: ['username'] + }, + { + foreignKeyName: 'user_profiles_username_fkey' + columns: ['username'] + isOneToOne: false + referencedRelation: 'users' + referencedColumns: ['username'] + } + ] + } + users: { + Row: { + age_range: unknown | null + catchphrase: unknown | null + data: Json | null + status: Database['public']['Enums']['user_status'] | null + username: string + } + Insert: { + age_range?: unknown | null + catchphrase?: unknown | null + data?: Json | null + status?: Database['public']['Enums']['user_status'] | null + username: string + } + Update: { + age_range?: unknown | null + catchphrase?: unknown | null + data?: Json | null + status?: Database['public']['Enums']['user_status'] | null + username?: string + } + Relationships: [] + } + } + Views: { + non_updatable_view: { + Row: { + username: string | null + } + Relationships: [] + } + updatable_view: { + Row: { + non_updatable_column: number | null + username: string | null + } + Insert: { + non_updatable_column?: never + username?: string | null + } + Update: { + non_updatable_column?: never + username?: string | null + } + Relationships: [] + } + } + Functions: { + function_with_array_param: { + Args: { + param: string[] + } + Returns: undefined + } + function_with_optional_param: { + Args: { + param?: string + } + Returns: string + } + get_status: { + Args: { + name_param: string + } + Returns: Database['public']['Enums']['user_status'] + } + get_username_and_status: { + Args: { + name_param: string + } + Returns: { + username: string + status: Database['public']['Enums']['user_status'] + }[] + } + offline_user: { + Args: { + name_param: string + } + Returns: Database['public']['Enums']['user_status'] + } + void_func: { + Args: Record + Returns: undefined + } + } + Enums: { + user_status: 'ONLINE' | 'OFFLINE' + } + CompositeTypes: { + [_ in never]: never + } + } +} + +type DefaultSchema = Database[Extract] + +export type Tables< + DefaultSchemaTableNameOrOptions extends + | keyof (DefaultSchema['Tables'] & DefaultSchema['Views']) + | { schema: keyof Database }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof Database + } + ? keyof (Database[DefaultSchemaTableNameOrOptions['schema']]['Tables'] & + Database[DefaultSchemaTableNameOrOptions['schema']]['Views']) + : never = never +> = DefaultSchemaTableNameOrOptions extends { schema: keyof Database } + ? (Database[DefaultSchemaTableNameOrOptions['schema']]['Tables'] & + Database[DefaultSchemaTableNameOrOptions['schema']]['Views'])[TableName] extends { + Row: infer R + } + ? R + : never + : DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema['Tables'] & DefaultSchema['Views']) + ? (DefaultSchema['Tables'] & DefaultSchema['Views'])[DefaultSchemaTableNameOrOptions] extends { + Row: infer R + } + ? R + : never + : never + +export type TablesInsert< + DefaultSchemaTableNameOrOptions extends + | keyof DefaultSchema['Tables'] + | { schema: keyof Database }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof Database + } + ? keyof Database[DefaultSchemaTableNameOrOptions['schema']]['Tables'] + : never = never +> = DefaultSchemaTableNameOrOptions extends { schema: keyof Database } + ? Database[DefaultSchemaTableNameOrOptions['schema']]['Tables'][TableName] extends { + Insert: infer I + } + ? I + : never + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema['Tables'] + ? DefaultSchema['Tables'][DefaultSchemaTableNameOrOptions] extends { + Insert: infer I + } + ? I + : never + : never + +export type TablesUpdate< + DefaultSchemaTableNameOrOptions extends + | keyof DefaultSchema['Tables'] + | { schema: keyof Database }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof Database + } + ? keyof Database[DefaultSchemaTableNameOrOptions['schema']]['Tables'] + : never = never +> = DefaultSchemaTableNameOrOptions extends { schema: keyof Database } + ? Database[DefaultSchemaTableNameOrOptions['schema']]['Tables'][TableName] extends { + Update: infer U + } + ? U + : never + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema['Tables'] + ? DefaultSchema['Tables'][DefaultSchemaTableNameOrOptions] extends { + Update: infer U + } + ? U + : never + : never + +export type Enums< + DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema['Enums'] | { schema: keyof Database }, + EnumName extends DefaultSchemaEnumNameOrOptions extends { + schema: keyof Database + } + ? keyof Database[DefaultSchemaEnumNameOrOptions['schema']]['Enums'] + : never = never +> = DefaultSchemaEnumNameOrOptions extends { schema: keyof Database } + ? Database[DefaultSchemaEnumNameOrOptions['schema']]['Enums'][EnumName] + : DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema['Enums'] + ? DefaultSchema['Enums'][DefaultSchemaEnumNameOrOptions] + : never + +export type CompositeTypes< + PublicCompositeTypeNameOrOptions extends + | keyof DefaultSchema['CompositeTypes'] + | { schema: keyof Database }, + CompositeTypeName extends PublicCompositeTypeNameOrOptions extends { + schema: keyof Database + } + ? keyof Database[PublicCompositeTypeNameOrOptions['schema']]['CompositeTypes'] + : never = never +> = PublicCompositeTypeNameOrOptions extends { schema: keyof Database } + ? Database[PublicCompositeTypeNameOrOptions['schema']]['CompositeTypes'][CompositeTypeName] + : PublicCompositeTypeNameOrOptions extends keyof DefaultSchema['CompositeTypes'] + ? DefaultSchema['CompositeTypes'][PublicCompositeTypeNameOrOptions] + : never + +export const Constants = { + personal: { + Enums: { + user_status: ['ONLINE', 'OFFLINE'], + }, + }, + public: { + Enums: { + user_status: ['ONLINE', 'OFFLINE'], + }, + }, +} as const diff --git a/test/types.override-with-options-postgrest13.ts b/test/types.override-with-options-postgrest13.ts new file mode 100644 index 00000000..56974416 --- /dev/null +++ b/test/types.override-with-options-postgrest13.ts @@ -0,0 +1,149 @@ +import type { Database as GeneratedDatabase } from './types.generated-with-options-postgrest13' +import { MergeDeep } from 'type-fest' + +export type CustomUserDataType = { + foo: string + bar: { + baz: number + } + en: 'ONE' | 'TWO' | 'THREE' + record: Record | null + recordNumber: Record | null +} + +export type Database = MergeDeep< + GeneratedDatabase, + { + personal: { + Tables: { + users: { + Row: { + data: CustomUserDataType | null + } + Insert: { + data?: CustomUserDataType | null + } + Update: { + data?: CustomUserDataType | null + } + } + } + } + public: { + Tables: { + users: { + Row: { + data: CustomUserDataType | null + } + Insert: { + data?: CustomUserDataType | null + } + Update: { + data?: CustomUserDataType | null + } + } + } + } + } +> + +type DefaultSchema = Database[Extract] + +export type Tables< + DefaultSchemaTableNameOrOptions extends + | keyof (DefaultSchema['Tables'] & DefaultSchema['Views']) + | { schema: keyof Database }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof Database + } + ? keyof (Database[DefaultSchemaTableNameOrOptions['schema']]['Tables'] & + Database[DefaultSchemaTableNameOrOptions['schema']]['Views']) + : never = never +> = DefaultSchemaTableNameOrOptions extends { schema: keyof Database } + ? (Database[DefaultSchemaTableNameOrOptions['schema']]['Tables'] & + Database[DefaultSchemaTableNameOrOptions['schema']]['Views'])[TableName] extends { + Row: infer R + } + ? R + : never + : DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema['Tables'] & DefaultSchema['Views']) + ? (DefaultSchema['Tables'] & DefaultSchema['Views'])[DefaultSchemaTableNameOrOptions] extends { + Row: infer R + } + ? R + : never + : never + +export type TablesInsert< + DefaultSchemaTableNameOrOptions extends + | keyof DefaultSchema['Tables'] + | { schema: keyof Database }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof Database + } + ? keyof Database[DefaultSchemaTableNameOrOptions['schema']]['Tables'] + : never = never +> = DefaultSchemaTableNameOrOptions extends { schema: keyof Database } + ? Database[DefaultSchemaTableNameOrOptions['schema']]['Tables'][TableName] extends { + Insert: infer I + } + ? I + : never + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema['Tables'] + ? DefaultSchema['Tables'][DefaultSchemaTableNameOrOptions] extends { + Insert: infer I + } + ? I + : never + : never + +export type TablesUpdate< + DefaultSchemaTableNameOrOptions extends + | keyof DefaultSchema['Tables'] + | { schema: keyof Database }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof Database + } + ? keyof Database[DefaultSchemaTableNameOrOptions['schema']]['Tables'] + : never = never +> = DefaultSchemaTableNameOrOptions extends { schema: keyof Database } + ? Database[DefaultSchemaTableNameOrOptions['schema']]['Tables'][TableName] extends { + Update: infer U + } + ? U + : never + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema['Tables'] + ? DefaultSchema['Tables'][DefaultSchemaTableNameOrOptions] extends { + Update: infer U + } + ? U + : never + : never + +export type Enums< + DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema['Enums'] | { schema: keyof Database }, + EnumName extends DefaultSchemaEnumNameOrOptions extends { + schema: keyof Database + } + ? keyof Database[DefaultSchemaEnumNameOrOptions['schema']]['Enums'] + : never = never +> = DefaultSchemaEnumNameOrOptions extends { schema: keyof Database } + ? Database[DefaultSchemaEnumNameOrOptions['schema']]['Enums'][EnumName] + : DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema['Enums'] + ? DefaultSchema['Enums'][DefaultSchemaEnumNameOrOptions] + : never + +export type CompositeTypes< + PublicCompositeTypeNameOrOptions extends + | keyof DefaultSchema['CompositeTypes'] + | { schema: keyof Database }, + CompositeTypeName extends PublicCompositeTypeNameOrOptions extends { + schema: keyof Database + } + ? keyof Database[PublicCompositeTypeNameOrOptions['schema']]['CompositeTypes'] + : never = never +> = PublicCompositeTypeNameOrOptions extends { schema: keyof Database } + ? Database[PublicCompositeTypeNameOrOptions['schema']]['CompositeTypes'][CompositeTypeName] + : PublicCompositeTypeNameOrOptions extends keyof DefaultSchema['CompositeTypes'] + ? DefaultSchema['CompositeTypes'][PublicCompositeTypeNameOrOptions] + : never