Skip to content

Commit 00c4a11

Browse files
authored
feat(postgrest): add embeded functions type inference (#1632)
1 parent 2d0bd77 commit 00c4a11

35 files changed

+3795
-735
lines changed

packages/core/postgrest-js/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"format": "node scripts/format.js",
3838
"format:check": "node scripts/format.js check",
3939
"build": "npm run clean && npm run build:cjs && npm run build:esm",
40+
"postbuild": "cp -rf ./src/types/common ../supabase-js/src/lib/rest/types/common",
4041
"build:cjs": "tsc -p tsconfig.json",
4142
"build:esm": "cpy wrapper.mjs dist/esm/",
4243
"docs": "typedoc src/index.ts --out docs/v2",
@@ -53,7 +54,7 @@
5354
"type-check:test": "tsc --noEmit --project tsconfig.test.json",
5455
"db:clean": "cd test/db && docker compose down --volumes",
5556
"db:run": "cd test/db && docker compose up --detach && wait-for-localhost 3000",
56-
"db:generate-test-types": "cd test/db && docker compose up --detach && wait-for-localhost 8080 && curl --location 'http://0.0.0.0:8080/generators/typescript?included_schemas=public,personal&detect_one_to_one_relationships=true' > ../types.generated.ts && node ../scripts/update-json-type.js && cd ../../ && npm run format"
57+
"db:generate-test-types": "cd test/db && docker compose up --detach && wait-for-localhost 8080 && wait-for-localhost 3000 && curl --location 'http://0.0.0.0:8080/generators/typescript?included_schemas=public,personal&detect_one_to_one_relationships=true' > ../types.generated.ts && node ../../scripts/update-json-type.js && cd ../../ && npm run format"
5758
},
5859
"dependencies": {
5960
"@supabase/node-fetch": "2.6.15"

packages/core/postgrest-js/test/scripts/update-json-type.js renamed to packages/core/postgrest-js/scripts/update-json-type.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const path = require('path')
88
* This is a cross-platform replacement for the sed command
99
*/
1010
function updateJsonType() {
11-
const filePath = path.join(__dirname, '..', 'types.generated.ts')
11+
const filePath = path.join(__dirname, '..', 'test', 'types.generated.ts')
1212

1313
try {
1414
// Read the file
@@ -23,7 +23,7 @@ function updateJsonType() {
2323
// Write the updated content back to the file
2424
fs.writeFileSync(filePath, updatedContent, 'utf8')
2525

26-
console.log('✅ Successfully updated Json type in types.generated.ts')
26+
console.log('✅ Successfully updated Json type in test/types.generated.ts')
2727
} catch (error) {
2828
console.error('❌ Error updating Json type:', error.message)
2929
process.exit(1)

packages/core/postgrest-js/src/PostgrestBuilder.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@
22
import nodeFetch from '@supabase/node-fetch'
33

44
import type {
5-
Fetch,
65
PostgrestSingleResponse,
76
PostgrestResponseSuccess,
87
CheckMatchingArrayTypes,
98
MergePartialResult,
109
IsValidResultOverride,
11-
ClientServerOptions,
12-
} from './types'
10+
} from './types/types'
11+
import { ClientServerOptions, Fetch } from './types/common/common'
1312
import PostgrestError from './PostgrestError'
1413
import { ContainsNull } from './select-query-parser/types'
1514

packages/core/postgrest-js/src/PostgrestClient.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import PostgrestQueryBuilder from './PostgrestQueryBuilder'
22
import PostgrestFilterBuilder from './PostgrestFilterBuilder'
3-
import { Fetch, GenericSchema, ClientServerOptions } from './types'
3+
import { Fetch, GenericSchema, ClientServerOptions } from './types/common/common'
4+
import { GetRpcFunctionFilterBuilderByArgs } from './types/common/rpc'
45

56
/**
67
* PostgREST client.
@@ -131,9 +132,17 @@ export default class PostgrestClient<
131132
* `"estimated"`: Uses exact count for low numbers and planned count for high
132133
* numbers.
133134
*/
134-
rpc<FnName extends string & keyof Schema['Functions'], Fn extends Schema['Functions'][FnName]>(
135+
rpc<
136+
FnName extends string & keyof Schema['Functions'],
137+
Args extends Schema['Functions'][FnName]['Args'] = never,
138+
FilterBuilder extends GetRpcFunctionFilterBuilderByArgs<
139+
Schema,
140+
FnName,
141+
Args
142+
> = GetRpcFunctionFilterBuilderByArgs<Schema, FnName, Args>,
143+
>(
135144
fn: FnName,
136-
args: Fn['Args'] = {},
145+
args: Args = {} as Args,
137146
{
138147
head = false,
139148
get = false,
@@ -146,14 +155,10 @@ export default class PostgrestClient<
146155
): PostgrestFilterBuilder<
147156
ClientOptions,
148157
Schema,
149-
Fn['Returns'] extends any[]
150-
? Fn['Returns'][number] extends Record<string, unknown>
151-
? Fn['Returns'][number]
152-
: never
153-
: never,
154-
Fn['Returns'],
155-
FnName,
156-
null,
158+
FilterBuilder['Row'],
159+
FilterBuilder['Result'],
160+
FilterBuilder['RelationName'],
161+
FilterBuilder['Relationships'],
157162
'RPC'
158163
> {
159164
let method: 'HEAD' | 'GET' | 'POST'

packages/core/postgrest-js/src/PostgrestFilterBuilder.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import PostgrestTransformBuilder from './PostgrestTransformBuilder'
22
import { JsonPathToAccessor, JsonPathToType } from './select-query-parser/utils'
3-
import { ClientServerOptions, GenericSchema } from './types'
3+
import { ClientServerOptions, GenericSchema } from './types/common/common'
44

55
type FilterOperator =
66
| 'eq'

packages/core/postgrest-js/src/PostgrestQueryBuilder.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import PostgrestFilterBuilder from './PostgrestFilterBuilder'
22
import { GetResult } from './select-query-parser/result'
3-
import { ClientServerOptions, Fetch, GenericSchema, GenericTable, GenericView } from './types'
3+
import {
4+
ClientServerOptions,
5+
Fetch,
6+
GenericSchema,
7+
GenericTable,
8+
GenericView,
9+
} from './types/common/common'
410

511
export default class PostgrestQueryBuilder<
612
ClientOptions extends ClientServerOptions,

packages/core/postgrest-js/src/PostgrestTransformBuilder.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
import PostgrestBuilder from './PostgrestBuilder'
2-
import { InvalidMethodError } from './PostgrestFilterBuilder'
2+
import PostgrestFilterBuilder, { InvalidMethodError } from './PostgrestFilterBuilder'
33
import { GetResult } from './select-query-parser/result'
4-
import {
5-
GenericSchema,
6-
CheckMatchingArrayTypes,
7-
ClientServerOptions,
8-
MaxAffectedEnabled,
9-
} from './types'
4+
import { CheckMatchingArrayTypes, MaxAffectedEnabled } from './types/types'
5+
import { ClientServerOptions, GenericSchema } from './types/common/common'
106

117
export default class PostgrestTransformBuilder<
128
ClientOptions extends ClientServerOptions,
@@ -31,11 +27,15 @@ export default class PostgrestTransformBuilder<
3127
NewResultOne = GetResult<Schema, Row, RelationName, Relationships, Query, ClientOptions>,
3228
>(
3329
columns?: Query
34-
): PostgrestTransformBuilder<
30+
): PostgrestFilterBuilder<
3531
ClientOptions,
3632
Schema,
3733
Row,
38-
NewResultOne[],
34+
Method extends 'RPC'
35+
? Result extends unknown[]
36+
? NewResultOne[]
37+
: NewResultOne
38+
: NewResultOne[],
3939
RelationName,
4040
Relationships,
4141
Method
@@ -56,11 +56,15 @@ export default class PostgrestTransformBuilder<
5656
.join('')
5757
this.url.searchParams.set('select', cleanedColumns)
5858
this.headers.append('Prefer', 'return=representation')
59-
return this as unknown as PostgrestTransformBuilder<
59+
return this as unknown as PostgrestFilterBuilder<
6060
ClientOptions,
6161
Schema,
6262
Row,
63-
NewResultOne[],
63+
Method extends 'RPC'
64+
? Result extends unknown[]
65+
? NewResultOne[]
66+
: NewResultOne
67+
: NewResultOne[],
6468
RelationName,
6569
Relationships,
6670
Method

packages/core/postgrest-js/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ export type {
2828
PostgrestResponseSuccess,
2929
PostgrestSingleResponse,
3030
PostgrestMaybeSingleResponse,
31-
ClientServerOptions as PostgrestClientOptions,
32-
} from './types'
31+
} from './types/types'
32+
export type { ClientServerOptions as PostgrestClientOptions } from './types/common/common'
3333
// https://github.com/supabase/postgrest-js/issues/551
3434
// To be replaced with a helper type that only uses public types
3535
export type { GetResult as UnstableGetResult } from './select-query-parser/result'

packages/core/postgrest-js/src/select-query-parser/parser.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Credits to @bnjmnt4n (https://www.npmjs.com/package/postgrest-query)
22
// See https://github.com/PostgREST/postgrest/blob/2f91853cb1de18944a4556df09e52450b881cfb3/src/PostgREST/ApiRequest/QueryParams.hs#L282-L284
33

4-
import { SimplifyDeep } from '../types'
4+
import { SimplifyDeep } from '../types/types'
55
import { JsonPathToAccessor } from './utils'
66

77
/**

packages/core/postgrest-js/src/select-query-parser/result.ts

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { ClientServerOptions, GenericTable } from '../types'
2-
import { ContainsNull, GenericRelationship, PostgreSQLTypes } from './types'
31
import { Ast, ParseQuery } from './parser'
42
import {
53
AggregateFunctions,
@@ -9,6 +7,11 @@ import {
97
Prettify,
108
TablesAndViews,
119
TypeScriptTypes,
10+
ContainsNull,
11+
GenericRelationship,
12+
PostgreSQLTypes,
13+
GenericTable,
14+
ClientServerOptions,
1215
} from './types'
1316
import {
1417
CheckDuplicateEmbededReference,
@@ -366,7 +369,7 @@ export type ProcessEmbeddedResource<
366369
ResolveRelationship<Schema, Relationships, Field, CurrentTableOrView> extends infer Resolved
367370
? Resolved extends {
368371
referencedTable: Pick<GenericTable, 'Row' | 'Relationships'>
369-
relation: GenericRelationship & { match: 'refrel' | 'col' | 'fkname' }
372+
relation: GenericRelationship & { match: 'refrel' | 'col' | 'fkname' | 'func' }
370373
direction: string
371374
}
372375
? ProcessEmbeddedResourceResult<ClientOptions, Schema, Resolved, Field, CurrentTableOrView>
@@ -385,7 +388,12 @@ type ProcessEmbeddedResourceResult<
385388
Schema extends GenericSchema,
386389
Resolved extends {
387390
referencedTable: Pick<GenericTable, 'Row' | 'Relationships'>
388-
relation: GenericRelationship & { match: 'refrel' | 'col' | 'fkname' }
391+
relation: GenericRelationship & {
392+
match: 'refrel' | 'col' | 'fkname' | 'func'
393+
isNotNullable?: boolean
394+
referencedRelation: string
395+
isSetofReturn?: boolean
396+
}
389397
direction: string
390398
},
391399
Field extends Ast.FieldNode,
@@ -395,7 +403,11 @@ type ProcessEmbeddedResourceResult<
395403
ClientOptions,
396404
Schema,
397405
Resolved['referencedTable']['Row'],
398-
Field['name'],
406+
// For embeded function selection, the source of truth is the 'referencedRelation'
407+
// coming from the SetofOptions.to parameter
408+
Resolved['relation']['match'] extends 'func'
409+
? Resolved['relation']['referencedRelation']
410+
: Field['name'],
399411
Resolved['referencedTable']['Relationships'],
400412
Field['children'] extends undefined
401413
? []
@@ -410,7 +422,18 @@ type ProcessEmbeddedResourceResult<
410422
? ProcessedChildren
411423
: ProcessedChildren[]
412424
: Resolved['relation']['isOneToOne'] extends true
413-
? ProcessedChildren | null
425+
? Resolved['relation']['match'] extends 'func'
426+
? Resolved['relation']['isNotNullable'] extends true
427+
? Resolved['relation']['isSetofReturn'] extends true
428+
? ProcessedChildren
429+
: // TODO: This shouldn't be necessary but is due in an inconsitency in PostgREST v12/13 where if a function
430+
// is declared with RETURNS <table-name> instead of RETURNS SETOF <table-name> ROWS 1
431+
// In case where there is no object matching the relations, the object will be returned with all the properties within it
432+
// set to null, we mimic this buggy behavior for type safety an issue is opened on postgREST here:
433+
// https://github.com/PostgREST/postgrest/issues/4234
434+
{ [P in keyof ProcessedChildren]: ProcessedChildren[P] | null }
435+
: ProcessedChildren | null
436+
: ProcessedChildren | null
414437
: ProcessedChildren[]
415438
: // If the relation is a self-reference it'll always be considered as reverse relationship
416439
Resolved['relation']['referencedRelation'] extends CurrentTableOrView

0 commit comments

Comments
 (0)