From 9f21996d4e03935270193669606270c5286a8d93 Mon Sep 17 00:00:00 2001 From: avallete Date: Mon, 20 Jan 2025 18:00:50 +0900 Subject: [PATCH 1/9] wip: reproduce https://github.com/supabase/supabase-js/issues/1354 --- package-lock.json | 37 ++++++-- package.json | 1 + test/issue-1354-d.ts | 215 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 246 insertions(+), 7 deletions(-) create mode 100644 test/issue-1354-d.ts diff --git a/package-lock.json b/package-lock.json index 55395c0b..a78cf6f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "ts-expect": "^1.3.0", "ts-jest": "^28.0.3", "tsd": "^0.31.2", + "type-fest": "^4.32.0", "typedoc": "^0.22.16", "typescript": "4.5.5", "wait-for-localhost-cli": "^3.0.0" @@ -1315,6 +1316,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -5904,12 +5918,13 @@ } }, "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "version": "4.32.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.32.0.tgz", + "integrity": "sha512-rfgpoi08xagF3JSdtJlCwMq9DGNDE0IMh3Mkpc1wUypg9vPi786AiqeBBKcqvIkq42azsBM85N490fyZjeUftw==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=10" + "node": ">=16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -7224,6 +7239,14 @@ "dev": true, "requires": { "type-fest": "^0.21.3" + }, + "dependencies": { + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true + } } }, "ansi-regex": { @@ -10554,9 +10577,9 @@ "dev": true }, "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "version": "4.32.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.32.0.tgz", + "integrity": "sha512-rfgpoi08xagF3JSdtJlCwMq9DGNDE0IMh3Mkpc1wUypg9vPi786AiqeBBKcqvIkq42azsBM85N490fyZjeUftw==", "dev": true }, "typedoc": { diff --git a/package.json b/package.json index ab56b8a7..5b2d4233 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "ts-expect": "^1.3.0", "ts-jest": "^28.0.3", "tsd": "^0.31.2", + "type-fest": "^4.32.0", "typedoc": "^0.22.16", "typescript": "4.5.5", "wait-for-localhost-cli": "^3.0.0" diff --git a/test/issue-1354-d.ts b/test/issue-1354-d.ts new file mode 100644 index 00000000..b9840a0a --- /dev/null +++ b/test/issue-1354-d.ts @@ -0,0 +1,215 @@ +import { expectType } from 'tsd' +import { PostgrestClient } from '../src/index' +import type { MergeDeep } from 'type-fest' + +export type Json = string | number | boolean | null | { [key: string]: Json | undefined } | Json[] + +export type Database = { + public: { + Tables: { + foo: { + Row: { + created_at: string | null + bar: Json + id: string + baz: Json + game_id: string + updated_at: string | null + user_id: string | null + } + Insert: { + created_at?: string | null + bar: Json + id?: string + baz: Json + game_id: string + updated_at?: string | null + user_id?: string | null + } + Update: { + created_at?: string | null + bar?: Json + id?: string + baz?: Json + game_id?: string + updated_at?: string | null + user_id?: string | null + } + Relationships: [] + } + } + Views: {} + Functions: {} + Enums: {} + CompositeTypes: { + [_ in never]: never + } + } +} + +type PublicSchema = Database[Extract] + +export type Tables< + PublicTableNameOrOptions extends + | keyof (PublicSchema['Tables'] & PublicSchema['Views']) + | { schema: keyof Database }, + TableName extends PublicTableNameOrOptions extends { schema: keyof Database } + ? keyof (Database[PublicTableNameOrOptions['schema']]['Tables'] & + Database[PublicTableNameOrOptions['schema']]['Views']) + : never = never +> = PublicTableNameOrOptions extends { schema: keyof Database } + ? (Database[PublicTableNameOrOptions['schema']]['Tables'] & + Database[PublicTableNameOrOptions['schema']]['Views'])[TableName] extends { + Row: infer R + } + ? R + : never + : PublicTableNameOrOptions extends keyof (PublicSchema['Tables'] & PublicSchema['Views']) + ? (PublicSchema['Tables'] & PublicSchema['Views'])[PublicTableNameOrOptions] extends { + Row: infer R + } + ? R + : never + : never + +export type TablesInsert< + PublicTableNameOrOptions extends keyof PublicSchema['Tables'] | { schema: keyof Database }, + TableName extends PublicTableNameOrOptions extends { schema: keyof Database } + ? keyof Database[PublicTableNameOrOptions['schema']]['Tables'] + : never = never +> = PublicTableNameOrOptions extends { schema: keyof Database } + ? Database[PublicTableNameOrOptions['schema']]['Tables'][TableName] extends { + Insert: infer I + } + ? I + : never + : PublicTableNameOrOptions extends keyof PublicSchema['Tables'] + ? PublicSchema['Tables'][PublicTableNameOrOptions] extends { + Insert: infer I + } + ? I + : never + : never + +export type TablesUpdate< + PublicTableNameOrOptions extends keyof PublicSchema['Tables'] | { schema: keyof Database }, + TableName extends PublicTableNameOrOptions extends { schema: keyof Database } + ? keyof Database[PublicTableNameOrOptions['schema']]['Tables'] + : never = never +> = PublicTableNameOrOptions extends { schema: keyof Database } + ? Database[PublicTableNameOrOptions['schema']]['Tables'][TableName] extends { + Update: infer U + } + ? U + : never + : PublicTableNameOrOptions extends keyof PublicSchema['Tables'] + ? PublicSchema['Tables'][PublicTableNameOrOptions] extends { + Update: infer U + } + ? U + : never + : never + +export type Enums< + PublicEnumNameOrOptions extends keyof PublicSchema['Enums'] | { schema: keyof Database }, + EnumName extends PublicEnumNameOrOptions extends { schema: keyof Database } + ? keyof Database[PublicEnumNameOrOptions['schema']]['Enums'] + : never = never +> = PublicEnumNameOrOptions extends { schema: keyof Database } + ? Database[PublicEnumNameOrOptions['schema']]['Enums'][EnumName] + : PublicEnumNameOrOptions extends keyof PublicSchema['Enums'] + ? PublicSchema['Enums'][PublicEnumNameOrOptions] + : never + +export type CompositeTypes< + PublicCompositeTypeNameOrOptions extends + | keyof PublicSchema['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 PublicSchema['CompositeTypes'] + ? PublicSchema['CompositeTypes'][PublicCompositeTypeNameOrOptions] + : never + +type Custom = { + version: number + events: Array<{ + type: string + [x: string]: any + }> +} + +export type DatabaseOverride = MergeDeep< + Database, + { + public: { + Tables: { + foo: { + Row: { + bar: Custom + baz: Custom + } + Insert: { + bar: Custom + baz: Custom + } + Update: { + bar?: Custom + baz?: Custom + } + } + } + } + } +> + +const postgrest = new PostgrestClient('http://localhost:3000') + +const postgrestOverrideTypes = new PostgrestClient('http://localhost:3000') + +// Basic types +{ + const res = await postgrest.from('foo').select('id').eq('id', '...').single() + + const bar = {} as Custom + const baz = {} as Custom + if (res.error) { + throw new Error(res.error.message) + } + const result = await postgrest + .from('foo') + .update({ + bar, + baz, + }) + .eq('id', res.data.id) + expectType(result.data) +} + +// extended types +{ + const res = await postgrestOverrideTypes + .from('foo') + .select('id, bar, baz') + .eq('id', '...') + .single() + + const bar = {} as Custom + const baz = {} as Custom + if (res.error) { + throw new Error(res.error.message) + } + const result = await postgrestOverrideTypes + .from('foo') + .update({ + bar, + baz, + }) + .eq('id', res.data.id) + expectType(result.data) +} From 8b7f24190fe497520f8ca8d14e5fc04987658dc9 Mon Sep 17 00:00:00 2001 From: avallete Date: Mon, 20 Jan 2025 18:01:35 +0900 Subject: [PATCH 2/9] wip: error occurs when typescript upgrade --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index a78cf6f5..76c86bb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,7 @@ "tsd": "^0.31.2", "type-fest": "^4.32.0", "typedoc": "^0.22.16", - "typescript": "4.5.5", + "typescript": "^4.7.4", "wait-for-localhost-cli": "^3.0.0" } }, @@ -5993,9 +5993,9 @@ } }, "node_modules/typescript": { - "version": "4.5.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", - "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -10629,9 +10629,9 @@ } }, "typescript": { - "version": "4.5.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", - "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", "dev": true }, "v8-to-istanbul": { diff --git a/package.json b/package.json index 5b2d4233..11224888 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "tsd": "^0.31.2", "type-fest": "^4.32.0", "typedoc": "^0.22.16", - "typescript": "4.5.5", + "typescript": "^4.7.4", "wait-for-localhost-cli": "^3.0.0" } } From c3aea0daf4be623241d730a2282f168a84c98304 Mon Sep 17 00:00:00 2001 From: avallete Date: Mon, 20 Jan 2025 18:22:08 +0900 Subject: [PATCH 3/9] chore: use direct replacement instead of MergeDeep --- test/issue-1354-d.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/issue-1354-d.ts b/test/issue-1354-d.ts index b9840a0a..4206e1a4 100644 --- a/test/issue-1354-d.ts +++ b/test/issue-1354-d.ts @@ -10,27 +10,27 @@ export type Database = { foo: { Row: { created_at: string | null - bar: Json + bar: Custom id: string - baz: Json + baz: Custom game_id: string updated_at: string | null user_id: string | null } Insert: { created_at?: string | null - bar: Json + bar: Custom id?: string - baz: Json + baz: Custom game_id: string updated_at?: string | null user_id?: string | null } Update: { created_at?: string | null - bar?: Json + bar?: Custom id?: string - baz?: Json + baz?: Custom game_id?: string updated_at?: string | null user_id?: string | null From 7816a4d3fe4d4dba16a94fb0cb6ee6c87a73483f Mon Sep 17 00:00:00 2001 From: avallete Date: Mon, 20 Jan 2025 22:26:21 +0900 Subject: [PATCH 4/9] fix(types): type instantiation is excessively deep and possibly infinite When using MergeDeep type-fest even with simple structures we get this error For typescript > 4.5.5 this trick fix the issue by forcing TS to infer the value And reuse it instead of computing multiple time --- src/PostgrestFilterBuilder.ts | 24 +++++++++++++++--------- test/issue-1354-d.ts | 12 ++++++------ 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/PostgrestFilterBuilder.ts b/src/PostgrestFilterBuilder.ts index 4dbee83c..8b39c8bd 100644 --- a/src/PostgrestFilterBuilder.ts +++ b/src/PostgrestFilterBuilder.ts @@ -73,9 +73,11 @@ export default class PostgrestFilterBuilder< */ eq( column: ColumnName, - value: ResolveFilterValue extends never - ? NonNullable - : NonNullable> + value: ResolveFilterValue extends infer ResolvedFilterValue + ? ResolvedFilterValue extends never + ? NonNullable + : NonNullable + : never ): this { this.url.searchParams.append(column, `eq.${value}`) return this @@ -89,9 +91,11 @@ export default class PostgrestFilterBuilder< */ neq( column: ColumnName, - value: ResolveFilterValue extends never - ? unknown - : ResolveFilterValue + value: ResolveFilterValue extends infer ResolvedFilterValue + ? ResolvedFilterValue extends never + ? unknown + : NonNullable + : never ): this { this.url.searchParams.append(column, `neq.${value}`) return this @@ -269,9 +273,11 @@ export default class PostgrestFilterBuilder< */ in( column: ColumnName, - values: ResolveFilterValue extends never - ? unknown[] - : ReadonlyArray> + values: ResolveFilterValue extends infer ResolvedFilterValue + ? ResolvedFilterValue extends never + ? unknown[] + : ReadonlyArray + : never ): this { const cleanedValues = Array.from(new Set(values)) .map((s) => { diff --git a/test/issue-1354-d.ts b/test/issue-1354-d.ts index 4206e1a4..b9840a0a 100644 --- a/test/issue-1354-d.ts +++ b/test/issue-1354-d.ts @@ -10,27 +10,27 @@ export type Database = { foo: { Row: { created_at: string | null - bar: Custom + bar: Json id: string - baz: Custom + baz: Json game_id: string updated_at: string | null user_id: string | null } Insert: { created_at?: string | null - bar: Custom + bar: Json id?: string - baz: Custom + baz: Json game_id: string updated_at?: string | null user_id?: string | null } Update: { created_at?: string | null - bar?: Custom + bar?: Json id?: string - baz?: Custom + baz?: Json game_id?: string updated_at?: string | null user_id?: string | null From 7ca20e7809d4ebce5c9de8d42e4ca41f9c2f01f5 Mon Sep 17 00:00:00 2001 From: avallete Date: Mon, 20 Jan 2025 22:27:59 +0900 Subject: [PATCH 5/9] chore: revert package --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 76c86bb4..3fb7db94 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,7 @@ "tsd": "^0.31.2", "type-fest": "^4.32.0", "typedoc": "^0.22.16", - "typescript": "^4.7.4", + "typescript": "^4.5.5", "wait-for-localhost-cli": "^3.0.0" } }, diff --git a/package.json b/package.json index 11224888..d46e8b73 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "tsd": "^0.31.2", "type-fest": "^4.32.0", "typedoc": "^0.22.16", - "typescript": "^4.7.4", + "typescript": "^4.5.5", "wait-for-localhost-cli": "^3.0.0" } } From 13a65ec7d386025ae222b07223ab4f72a145c815 Mon Sep 17 00:00:00 2001 From: avallete Date: Mon, 20 Jan 2025 22:48:06 +0900 Subject: [PATCH 6/9] fix: in filter result infer --- src/PostgrestFilterBuilder.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/PostgrestFilterBuilder.ts b/src/PostgrestFilterBuilder.ts index 8b39c8bd..489b7637 100644 --- a/src/PostgrestFilterBuilder.ts +++ b/src/PostgrestFilterBuilder.ts @@ -273,11 +273,13 @@ export default class PostgrestFilterBuilder< */ in( column: ColumnName, - values: ResolveFilterValue extends infer ResolvedFilterValue - ? ResolvedFilterValue extends never - ? unknown[] - : ReadonlyArray - : never + values: ReadonlyArray< + ResolveFilterValue extends infer ResolvedFilterValue + ? ResolvedFilterValue extends never + ? unknown[] + : ResolvedFilterValue + : never + > ): this { const cleanedValues = Array.from(new Set(values)) .map((s) => { From e7089f620c685bfa62090f605fbce9e1b80e850c Mon Sep 17 00:00:00 2001 From: avallete Date: Mon, 20 Jan 2025 23:24:10 +0900 Subject: [PATCH 7/9] fix: in filter --- src/PostgrestFilterBuilder.ts | 6 +++--- test/issue-1354-d.ts | 13 +++++++++++++ test/relationships.ts | 7 ++++++- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/PostgrestFilterBuilder.ts b/src/PostgrestFilterBuilder.ts index 489b7637..4b47beb8 100644 --- a/src/PostgrestFilterBuilder.ts +++ b/src/PostgrestFilterBuilder.ts @@ -75,9 +75,9 @@ export default class PostgrestFilterBuilder< column: ColumnName, value: ResolveFilterValue extends infer ResolvedFilterValue ? ResolvedFilterValue extends never - ? NonNullable + ? unknown : NonNullable - : never + : unknown ): this { this.url.searchParams.append(column, `eq.${value}`) return this @@ -95,7 +95,7 @@ export default class PostgrestFilterBuilder< ? ResolvedFilterValue extends never ? unknown : NonNullable - : never + : unknown ): this { this.url.searchParams.append(column, `neq.${value}`) return this diff --git a/test/issue-1354-d.ts b/test/issue-1354-d.ts index b9840a0a..e388bf30 100644 --- a/test/issue-1354-d.ts +++ b/test/issue-1354-d.ts @@ -212,4 +212,17 @@ const postgrestOverrideTypes = new PostgrestClient('http://loc }) .eq('id', res.data.id) expectType(result.data) + const resIn = await postgrestOverrideTypes + .from('foo') + .select('id, bar, baz') + .in('bar', [ + { version: 1, events: [] }, + { version: 2, events: [] }, + ]) + .single() + + if (resIn.error) { + throw new Error(resIn.error.message) + } + expectType<{ id: string; bar: Custom; baz: Custom }>(resIn.data) } diff --git a/test/relationships.ts b/test/relationships.ts index faca706a..7c4110aa 100644 --- a/test/relationships.ts +++ b/test/relationships.ts @@ -1830,7 +1830,12 @@ test('self reference relation via column', async () => { }) test('aggregate on missing column with alias', async () => { - const res = await selectQueries.aggregateOnMissingColumnWithAlias.eq('id', 1).limit(1).single() + const res = await selectQueries.aggregateOnMissingColumnWithAlias + // @ts-expect-error should not be able to eq 'id' since the column does not + // exist + .eq('id', 2) + .limit(1) + .single() expect(res).toMatchInlineSnapshot(` Object { "count": null, From b49c66098b94ccec2be4e3e598aaa4eac2a19151 Mon Sep 17 00:00:00 2001 From: avallete Date: Thu, 23 Jan 2025 14:41:23 +0900 Subject: [PATCH 8/9] fix: jsonpath accessor and filters --- src/PostgrestFilterBuilder.ts | 43 +++++++++++++++--------- test/issue-1354-d.ts | 62 +++++++++++++++++++++++++++++++++++ test/relationships.ts | 7 +--- 3 files changed, 91 insertions(+), 21 deletions(-) diff --git a/src/PostgrestFilterBuilder.ts b/src/PostgrestFilterBuilder.ts index 4b47beb8..8fe560bb 100644 --- a/src/PostgrestFilterBuilder.ts +++ b/src/PostgrestFilterBuilder.ts @@ -1,4 +1,5 @@ import PostgrestTransformBuilder from './PostgrestTransformBuilder' +import { JsonPathToAccessor, JsonPathToType } from './select-query-parser/utils' import { GenericSchema } from './types' type FilterOperator = @@ -40,6 +41,12 @@ type ResolveFilterValue< : ResolveFilterRelationshipValue : ColumnName extends keyof Row ? Row[ColumnName] + : // If the column selection is a jsonpath like `data->value` we attempt to match + // the expected type with the parsed custom json type + JsonPathToType> extends infer JsonPathValue + ? JsonPathValue extends never + ? never + : JsonPathValue : never type ResolveFilterRelationshipValue< @@ -73,11 +80,14 @@ export default class PostgrestFilterBuilder< */ eq( column: ColumnName, - value: ResolveFilterValue extends infer ResolvedFilterValue - ? ResolvedFilterValue extends never - ? unknown - : NonNullable - : unknown + value: ResolveFilterValue extends never + ? NonNullable + : // We want to infer the type before wrapping it into a `NonNullable` to avoid too deep + // type resolution error + ResolveFilterValue extends infer ResolvedFilterValue + ? NonNullable + : // We should never enter this case as all the branches are covered above + never ): this { this.url.searchParams.append(column, `eq.${value}`) return this @@ -91,11 +101,11 @@ export default class PostgrestFilterBuilder< */ neq( column: ColumnName, - value: ResolveFilterValue extends infer ResolvedFilterValue - ? ResolvedFilterValue extends never - ? unknown - : NonNullable - : unknown + value: ResolveFilterValue extends never + ? unknown + : ResolveFilterValue extends infer ResolvedFilterValue + ? ResolvedFilterValue + : never ): this { this.url.searchParams.append(column, `neq.${value}`) return this @@ -274,11 +284,14 @@ export default class PostgrestFilterBuilder< in( column: ColumnName, values: ReadonlyArray< - ResolveFilterValue extends infer ResolvedFilterValue - ? ResolvedFilterValue extends never - ? unknown[] - : ResolvedFilterValue - : never + ResolveFilterValue extends never + ? unknown + : // We want to infer the type before wrapping it into a `NonNullable` to avoid too deep + // type resolution error + ResolveFilterValue extends infer ResolvedFilterValue + ? ResolvedFilterValue + : // We should never enter this case as all the branches are covered above + never > ): this { const cleanedValues = Array.from(new Set(values)) diff --git a/test/issue-1354-d.ts b/test/issue-1354-d.ts index e388bf30..704f0ae7 100644 --- a/test/issue-1354-d.ts +++ b/test/issue-1354-d.ts @@ -191,6 +191,35 @@ const postgrestOverrideTypes = new PostgrestClient('http://loc expectType(result.data) } +// basic types with postgres jsonpath selector +{ + const res = await postgrest.from('foo').select('id, bar, baz').eq('bar->version', 31).single() + + const bar = {} as Json + const baz = {} as Json + if (res.error) { + throw new Error(res.error.message) + } + const result = await postgrest + .from('foo') + .update({ + bar, + baz, + }) + .eq('bar->version', 31) + expectType(result.data) + const resIn = await postgrest + .from('foo') + .select('id, bar, baz') + .in('bar->version', [1, 2]) + .single() + + if (resIn.error) { + throw new Error(resIn.error.message) + } + expectType<{ id: string; bar: Json; baz: Json }>(resIn.data) +} + // extended types { const res = await postgrestOverrideTypes @@ -226,3 +255,36 @@ const postgrestOverrideTypes = new PostgrestClient('http://loc } expectType<{ id: string; bar: Custom; baz: Custom }>(resIn.data) } + +// extended types with postgres jsonpath selector +{ + const res = await postgrestOverrideTypes + .from('foo') + .select('id, bar, baz') + .eq('bar->version', 31) + .single() + + const bar = {} as Custom + const baz = {} as Custom + if (res.error) { + throw new Error(res.error.message) + } + const result = await postgrestOverrideTypes + .from('foo') + .update({ + bar, + baz, + }) + .eq('bar->version', res.data.bar.version) + expectType(result.data) + const resIn = await postgrestOverrideTypes + .from('foo') + .select('id, bar, baz') + .in('bar->version', [1, 32]) + .single() + + if (resIn.error) { + throw new Error(resIn.error.message) + } + expectType<{ id: string; bar: Custom; baz: Custom }>(resIn.data) +} diff --git a/test/relationships.ts b/test/relationships.ts index 7c4110aa..bf40a1f1 100644 --- a/test/relationships.ts +++ b/test/relationships.ts @@ -1830,12 +1830,7 @@ test('self reference relation via column', async () => { }) test('aggregate on missing column with alias', async () => { - const res = await selectQueries.aggregateOnMissingColumnWithAlias - // @ts-expect-error should not be able to eq 'id' since the column does not - // exist - .eq('id', 2) - .limit(1) - .single() + const res = await selectQueries.aggregateOnMissingColumnWithAlias.eq('id', 2).limit(1).single() expect(res).toMatchInlineSnapshot(` Object { "count": null, From c2112d4a1e618746ccd140bd110509eb127a727e Mon Sep 17 00:00:00 2001 From: avallete Date: Thu, 23 Jan 2025 14:58:29 +0900 Subject: [PATCH 9/9] fix: jsonpath filter string operator accessor --- src/PostgrestFilterBuilder.ts | 10 ++++++++-- test/issue-1354-d.ts | 8 +++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/PostgrestFilterBuilder.ts b/src/PostgrestFilterBuilder.ts index 8fe560bb..c0de7d33 100644 --- a/src/PostgrestFilterBuilder.ts +++ b/src/PostgrestFilterBuilder.ts @@ -26,6 +26,10 @@ type FilterOperator = | 'phfts' | 'wfts' +export type IsStringOperator = Path extends `${string}->>${string}` + ? true + : false + // Match relationship filters with `table.column` syntax and resolve underlying // column value. If not matched, fallback to generic type. // TODO: Validate the relationship itself ala select-query-parser. Currently we @@ -41,9 +45,11 @@ type ResolveFilterValue< : ResolveFilterRelationshipValue : ColumnName extends keyof Row ? Row[ColumnName] - : // If the column selection is a jsonpath like `data->value` we attempt to match + : // If the column selection is a jsonpath like `data->value` or `data->>value` we attempt to match // the expected type with the parsed custom json type - JsonPathToType> extends infer JsonPathValue + IsStringOperator extends true + ? string + : JsonPathToType> extends infer JsonPathValue ? JsonPathValue extends never ? never : JsonPathValue diff --git a/test/issue-1354-d.ts b/test/issue-1354-d.ts index 704f0ae7..247efa43 100644 --- a/test/issue-1354-d.ts +++ b/test/issue-1354-d.ts @@ -280,7 +280,13 @@ const postgrestOverrideTypes = new PostgrestClient('http://loc const resIn = await postgrestOverrideTypes .from('foo') .select('id, bar, baz') - .in('bar->version', [1, 32]) + .in('bar->version', [31]) + .single() + await postgrestOverrideTypes + .from('foo') + .select('id, bar, baz') + // the type become a string when using the string json accessor operator + .in('bar->>version', ['something']) .single() if (resIn.error) {